How to replace KVO code with RAC3 and save existing models?

I am learning ReactiveCocoa to improve our Swift code. As a starting point, I would like to bind the label text to the converted property value. Basically, I would like to replace some KVO code. So, I have the following variables:

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

Since our models were developed in Objective-C, MyModel as follows:

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

So, I would like to do something like this:

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

However, this obviously does not work. How to approach the problem? And how is KVO implemented with normal model properties?

I have already found a similar entry regarding this topic. The accepted answer suggests using ViewModels. However, instead of replacing existing NSManagedObject models with ViewModels, I want to save my models, since I like to manage them through Xcode and store them through CoreData. Or is it possible for ViewModels? Did I miss something important?

+5
source share
1 answer

Something like this (built with Xcode 6.4 using 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 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 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 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 your example, you would call it:

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

I'm a little new to ReactiveCocoa, so the above is probably a naive implementation, but it might point you in the right direction.

Update This bind implementation using channels is somewhat more compact and uses the RAC built in to support 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) } 
+1
source

All Articles