首页 > 解决方案 > 如何更新此类以支持将键路径值绑定到函数?

问题描述

我有一个类允许我将属性声明为Bindable

let user: Bindable<User> = Bindable(someUser)
user.update(with: someNewUser)
......
user.bind(\.name, to: label, \.text)

这样做允许将更改直接绑定到 UI 元素。

这是基于在此处找到的一篇文章

import Foundation

final class Bindable<Value> {
    private var observations = [(Value) -> Bool]()
    private var lastValue: Value?

    init(_ value: Value? = nil) {
        lastValue = value
    }
}

extension Bindable {
    func update(with value: Value) {
        lastValue = value
        observations = observations.filter { $0(value) }
    }
}

extension Bindable {
    // Non Optionals
    func bind<O: AnyObject, T>(_ sourceKeyPath: KeyPath<Value, T>, to object: O, _ objectKeyPath: ReferenceWritableKeyPath<O, T>) {
        addObservation(for: object) { object, observed in
            let value = observed[keyPath: sourceKeyPath]
            object[keyPath: objectKeyPath] = value
        }
    }
    
    
    
    // Optionals
    func bind<O: AnyObject, T>(_ sourceKeyPath: KeyPath<Value, T>, to object: O, _ objectKeyPath: ReferenceWritableKeyPath<O, T?>) {
        addObservation(for: object) { object, observed in
            let value = observed[keyPath: sourceKeyPath]
            object[keyPath: objectKeyPath] = value
        }
    }
}

private extension Bindable {
    func addObservation<O: AnyObject>(for object: O, handler: @escaping (O, Value) -> Void) {
        // If we already have a value available, give the  handler access to it directly.
        lastValue.map { handler(object, $0) }

        // Each observation closure returns a Bool that indicates
        // whether the observation should still be kept alive,
        // based on whether the observing object is still retained.
        observations.append { [weak object] value in
            guard let object = object else { return false }

            handler(object, value)
            return true
        }
    }
}

我想做的也是能够将属性绑定到函数。

绑定值的当前语法类似于 -

user.bind(\.name, to: label, \.text)

但我想扩展它,以便该键路径上的属性可以调用一个方法。

有点像 -


func doSomethingWithProp(_ prop: String) {
    // do something
}

user.bind(\.name, to: doSomething)

在这种情况下doSomething,可以调用一个助手NSAttributedString并接受该name道具作为要在该助手中使用的参数。

我在RxSwift使用bind(onNext: ....).

我尝试使用 -

    func bind<O: AnyObject, T, P>(_ sourceKeyPatch: KeyPath<Value, T>, to onNext: @escaping (P) -> Void) {
        addObservation(for: onNext) { onNext, observed in
             let value = observed[keyPath: sourceKeyPath]
             onNext(value)
         }
    }

位我收到以下错误-

函数签名中未使用通用参数“O”

无法推断通用参数“O”

标签: swiftreactiveswift-keypath

解决方案


这种 Bindable 方法期望有一些观察对象,但你没有。也就是说,它实际上并不关心那个对象是什么。它只是传回给处理程序的东西。self因此,您可以通过使用作为占位符对象以这种方式在扩展中处理此问题:

func bind<T>(_ sourceKeyPath: KeyPath<Value, T>, onNext: @escaping (T) -> Void) {
    addObservation(for: self) { object, observed in
        let value = observed[keyPath: sourceKeyPath]
        onNext(value)
     }
}

也就是说,这感觉有点乱,所以我可能会重新设计 Bindable 以原生支持它,并在它之上构建对象绑定。addObservation只需调用一个处理程序,就可以减少私有操作:

private extension Bindable {
    func addObservation(handler: @escaping (Value) -> Bool) { // <== Require a bool here
        lastValue.map { handler($0) }

        observations.append { handler($0) }  // <== Just call the handler
    }
}

并使所有公共方法对对象进行更多检查,因此私有扩展不必知道它。:

extension Bindable {
    // Non Optionals
    func bind<O: AnyObject, T>(_ sourceKeyPath: KeyPath<Value, T>, to object: O, _ objectKeyPath: ReferenceWritableKeyPath<O, T>) {
        addObservation { [weak object] observed in
            guard let object = object else { return false }  // <== Have to check object here instead
            let value = observed[keyPath: sourceKeyPath]
            object[keyPath: objectKeyPath] = value
            return true
        }
    }

    // Optionals
    func bind<O: AnyObject, T>(_ sourceKeyPath: KeyPath<Value, T>, to object: O, _ objectKeyPath: ReferenceWritableKeyPath<O, T?>) {
        addObservation { [weak object] observed in
            guard let object = object else { return false }
            let value = observed[keyPath: sourceKeyPath]
            object[keyPath: objectKeyPath] = value
            return true
        }
    }

    // Function
    func bind<T>(_ sourceKeyPath: KeyPath<Value, T>, onNext: @escaping (T) -> Void) {
        addObservation { observed in
            let value = observed[keyPath: sourceKeyPath]
            onNext(value)
            return true
         }
    }
}

您可能可以在此处进行更多重构以减少一些代码重复,但关键是要减少原始处理程序的工作量。

请注意,在 iOS 13+ 中,这应该使用 Combine 来完成。它以更强大的方式完成这一切。


推荐阅读