5

我正在研究 ReactiveCocoa 以改进我们的 Swift 代码。作为起点,我想将标签的文本绑定到属性的转换值。基本上,我想替换一些 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 一般是如何用正常的模型属性实现的?

我已经找到了关于这个主题的类似帖子。那里接受的答案建议使用 ViewModels。但是,我不想用 ViewModels 替换我现有的 NSManagedObject 模型,而是想保留我的模型,因为我喜欢通过 XCode 管理它们并通过 CoreData 存储它们。或者这对于 ViewModel 来说是否也是可能的?我错过了什么重要的东西吗?

4

1 回答 1

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 的内置 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)
}
于 2015-09-22T07:52:20.000 回答