如何更新此 class 以支持将键路径值绑定到函数?

How can I update this class to support binding a keypath value to a function?

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

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

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

这是基于找到的一篇文章 here

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 { [=12=](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, [=12=]) }

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

位我得到以下错误-

Generic parameter 'O' is not used in function signature

Generic parameter 'O' could not be inferred

这种可绑定方法期望有一些观察对象,但您没有。也就是说,它实际上并不关心那个对象是什么。这只是传递回处理程序的东西。因此,您可以通过使用 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([=11=]) }

        observations.append { handler([=11=]) }  // <== Just call the handler
    }
}

并使所有 public 方法对对象做更多的检查,因此私有扩展不必知道它。:

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 来完成。它以更强大的方式完成这一切。