5

Sto cercando ReactiveCocoa per migliorare il nostro codice Swift. Come punto di partenza, vorrei associare il testo di un'etichetta al valore trasformato di una proprietà. Fondamentalmente, vorrei sostituire qualche codice KVO. Così, ho le seguenti variabili:Come sostituire il mio codice KVO con RAC3 e mantenere i miei modelli esistenti?

@IBOutlet weak var myLabel: UILabel! 
var myModel: MyModel 

Dato che i nostri modelli sono stati sviluppati in Objective-C, MyModel assomiglia a questo:

@interface MyModel : NSManagedObject 
@property (nonatomic, retain) NSNumber * value; 
@end 

Così, mi piacerebbe fare qualcosa di simile a questa:

myLabel.text <~ myProperty.rac_signalForSelector("value") 
    |> map { (value: NSNumber) in 
     return "\(value.integerValue + 1)" 
    } 

Tuttavia, questo ovviamente non funziona. Come posso affrontare il problema? E come si realizza generalmente KVO con le normali proprietà del modello?

Ho già trovato un similar post per quanto riguarda questo argomento. La risposta accettata suggerisce di utilizzare ViewModels. Tuttavia, invece di sostituire i miei modelli NSManagedObject esistenti con ViewModels, voglio conservare i miei modelli poiché mi piace gestirli tramite XCode e archiviarli tramite CoreData. O è in qualche modo possibile anche per ViewModels? Mi manca qualcosa di cruciale?

risposta

1

Che ne dite di qualcosa di simile (costruito con Xcode 6.4 utilizzando RAC 3.0.0):

/// Send changes in the property value of a source object to another property on a target object, 
/// optionally using a mapping function to convert values as necessary. 
func bind<S, T>(propertyWithKey targetKey: String, on target: NSObject, toPropertyWithKey sourceKey: String, on source:NSObject, usingValueConversion sourceToTargetValueConversion: ((S) -> T)?) { 

    var (source_signal, source_sink) = Signal<T, NSError>.pipe() 

    var sourceSignalProducer = RACObserve(source, sourceKey).toSignalProducer() 
     |> start(next: {value in 
      if (value != nil) { 
       if (sourceToTargetValueConversion == nil) { 
        sendNext(source_sink, value as! T) 
       } else { 
        let mappedValue = sourceToTargetValueConversion!(value as! S) 
        sendNext(source_sink, mappedValue) 
       } 
      } 
     }) 

    var convertedValueRACSignal = toRACSignal(
     source_signal 
      |> map {(value: T) in value as! AnyObject} 
    ) 

    convertedValueRACSignal ~> RAC(target, targetKey) 
} 

/// Send changes in the property value of a source object to another property on a target object. 
/// No conversion of values or value types is performed between source and target. 
func bind(propertyWithKey targetKey: String, on target: NSObject, toPropertyWithKey sourceKey: String, on source:NSObject) { 

    var (source_signal, source_sink) = Signal<AnyObject, NSError>.pipe() 

    var sourceSignalProducer = RACObserve(source, sourceKey).toSignalProducer() 
     |> start(next: {value in 
      if (value != nil) { 
       sendNext(source_sink, value!) 
      } 
     }) 

    var convertedValueRACSignal = toRACSignal(source_signal) 

    convertedValueRACSignal ~> RAC(target, targetKey) 
} 



// From Colin Eberhardt's post at http://blog.scottlogic.com/2014/07/24/mvvm-reactivecocoa-swift.html 
// a struct that replaces the RAC macro 
struct RAC { 
    var target : NSObject! 
    var keyPath : String! 
    var nilValue : AnyObject! 

    init(_ target: NSObject!, _ keyPath: String, nilValue: AnyObject? = nil) { 
     self.target = target 
     self.keyPath = keyPath 
     self.nilValue = nilValue 
    } 


    func assignSignal(signal : RACSignal) { 
     signal.setKeyPath(self.keyPath, onObject: self.target, nilValue: self.nilValue) 
    } 
} 

// From Colin Eberhardt's post at http://blog.scottlogic.com/2014/07/24/mvvm-reactivecocoa-swift.html 
infix operator ~> {} 
func ~> (signal: RACSignal, rac: RAC) { 
    rac.assignSignal(signal) 
} 

// From Colin Eberhardt's post at http://blog.scottlogic.com/2014/07/24/mvvm-reactivecocoa-swift.html 
func RACObserve(target: NSObject!, keyPath: String) -> RACSignal { 
    return target.rac_valuesForKeyPath(keyPath, observer: target) 
} 

Nel tuo esempio che ci si chiami:

bind(propertyWithKey: "text", on: myTextField, toPropertyWithKey: "value", on: myProperty) 
      { (number: NSNumber) in 
       return "\(number.integerValue)" as NSString 
      } 

Sono un po 'di nuovo da ReactiveCocoa, quindi quanto sopra è probabilmente un'implementazione ingenua, ma potrebbe indirizzarti nella giusta direzione.

Aggiornamento Questa implementazione di bind utilizzando canali è un po 'più compatto e sfrutta RAC di un supporto incorporato KVO:

func bind<S, T>(propertyWithKey targetKey: String, on target: NSObject, toPropertyWithKey sourceKey: String, on source:NSObject, usingValueConversion sourceToTargetValueConversion: ((S) -> T)?) { 

    var kvoChannelSource = RACKVOChannel(target: source, keyPath: sourceKey, nilValue: nil) 
    var sourceSignalWithoutNils = kvoChannelSource.followingTerminal 
     .filter { (var value:AnyObject?) -> Bool in 
      return (value != nil) 
     } 

    var mappedSourceSignal = sourceSignalWithoutNils 
    if (sourceToTargetValueConversion != nil) 
    { 
     mappedSourceSignal = sourceSignalWithoutNils.map { (var s: AnyObject!) -> AnyObject! in 
      var result : T = sourceToTargetValueConversion!(s as! S) 
      return result as! AnyObject 
     } 
    } 

    var kvoChannelTarget = RACKVOChannel(target: target, keyPath: targetKey, nilValue: nil) 
    mappedSourceSignal.subscribe(kvoChannelTarget.followingTerminal) 
} 
Problemi correlati