5

Ich untersuche ReactiveCocoa, um unseren Swift-Code zu verbessern. Als Ausgangspunkt möchte ich den Text eines Labels an den transformierten Wert einer Eigenschaft binden. Grundsätzlich möchte ich einige KVO-Codes ersetzen. Also, ich habe die folgenden Variablen:Wie ersetze ich meinen KVO-Code durch RAC3 und behalte meine bestehenden Modelle?

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

Da unsere Modelle wurden in Objective-C entwickelt, sieht MyModel wie folgt aus:

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

So, ich möchte so etwas tun:

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

Dies funktioniert jedoch offensichtlich nicht. Wie soll ich das Problem angehen? Und wie wird KVO generell mit normalen Modelleigenschaften realisiert?

Ich habe bereits eine similar post zu diesem Thema gefunden. Die angenommene Antwort schlägt vor, ViewModels zu verwenden. Anstatt meine vorhandenen NSManagedObject-Modelle durch ViewModels zu ersetzen, möchte ich meine Modelle behalten, da ich sie gerne über XCode verwalten und über CoreData speichern möchte. Oder ist das auch irgendwie für ViewModels möglich? Fehle ich etwas Entscheidendes?

Antwort

1

Wie wäre es damit so etwas wie (gebaut mit Xcode 6.4 RAC mit 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) 
} 

In Ihrem Beispiel würden Sie es nennen:

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

ich etwas bin neu zu ReactiveCocoa, also ist das Obige wahrscheinlich eine naive Implementierung, aber es kann Sie in die richtige Richtung weisen.

aktualisieren Diese Implementierung von bind unter Verwendung von Kanälen ist etwas kompakter und nutzt in KVO Unterstützung gebaut RAC ist:

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) 
} 
Verwandte Themen