5

我正在研究ReactiveCocoa來改進我們的Swift代碼。作爲一個起點,我想將標籤的文本綁定到屬性的轉換後的值。基本上,我想替換一些KVO代碼。所以,我有以下變量:如何用RAC3替換我的KVO代碼並保留現有型號?

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

由於我們的模型在Objective-C開發了,MyModel看起來是這樣的:

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

因此,我願做這樣的事情:

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

但是,這顯然不起作用。我該如何處理這個問題? KVO通常如何用普通的模型屬性來實現?

我已經找到關於此主題的similar post。那裏接受的答案建議使用ViewModels。但是,我不想用ViewModel替換現有的NSManagedObject模型,而是希望保留我的模型,因爲我喜歡通過XCode管理它們並通過CoreData存儲它們。或者這對於ViewModels來說也是可能的嗎?我是否缺少重要的東西?

回答

1

如何這樣的事情(使用RAC 3.0.0和Xcode 6.4內置):

/// 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) 
} 

在您的例子就叫做:

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

我有點新的ReactiveCocoa,所以上述可能是一個天真的實現,但它可能會指出你在正確的方向。

更新 bind運用渠道的這種實現是較爲緊湊,並利用內置的志願支持RAC的:

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) 
} 
相關問題