5

Я изучаю ReactiveCocoa, чтобы улучшить наш код Swift. В качестве отправной точки я хотел бы привязать текст метки к преобразованному значению свойства. В принципе, я хотел бы заменить некоторый код KVO. Итак, у меня есть следующие переменные:Как заменить код KVO на RAC3 и сохранить существующие модели?

@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. Однако вместо замены существующих моделей NSManagedObject на ViewModels я хочу сохранить свои модели, так как мне нравится управлять ими через XCode и хранить их через CoreData. Или это возможно для ViewModels? Не хватает ли чего-нибудь важного?

ответ

1

Как о чем-то вроде этого (построен с Xcode 6.4 с помощью 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) 
} 

В вашем примере вы бы назвали:

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

Я несколько новых для ReactiveCocoa, поэтому приведенное выше, вероятно, является наивной реализацией, но оно может указывать на вас в правильном направлении.

Update Эта реализация 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) 
} 

 Смежные вопросы

  • Нет связанных вопросов^_^