gpt4 book ai didi

ios - 绑定(bind)模型和 View : how to observe object properties

转载 作者:行者123 更新时间:2023-12-01 16:20:12 26 4
gpt4 key购买 nike

我有一个结构类似于创建模型对象的表单的 View 。我正在尝试将表单元素( UIControl )绑定(bind)到模型属性,以便 View 在其相应的模型属性更改时自动更新,并在控件更改时更新模型(双向绑定(bind))。模型可以在 View 不知道的情况下更改,因为多个 View 可以链接到一个相同的模型属性。

方法 1:普通 Swift

我的问题如下:为了观察模型属性的变化,我尝试使用 KVO in Swift ,特别是 observe(_:changeHandler:)方法。

class Binding<View: NSObject, Object: NSObject, ValueType> {
weak var object: Object?
weak var view: View?

var objectToViewObservation: NSKeyValueObservation?
var viewToObjectObservation: NSKeyValueObservation?

private var objectKeyPath: WritableKeyPath<Object, ValueType>
private var viewKeyPath: WritableKeyPath<View, ValueType>

init(betweenObject objectKeyPath: WritableKeyPath<Object, ValueType>,
andView viewKeyPath: WritableKeyPath<View, ValueType>) {
self.objectKeyPath = objectKeyPath
self.viewKeyPath = viewKeyPath
}

override func bind(_ object: Object, with view: View) {
super.bind(object, with: view)
self.object = object
self.view = view

// initial value from object to view
self.view![keyPath: viewKeyPath] = self.object![keyPath: objectKeyPath]

// object --> view
objectToViewObservation = object.observe(objectKeyPath) { _, change in
guard var view = self.view else {
// view doesn't exist anymore
self.objectToViewObservation = nil
return
}

guard let value = change.newValue else { return }
view[keyPath: self.viewKeyPath] = value
}

// view --> object
viewToObjectObservation = view.observe(viewKeyPath) { _, change in
guard var object = self.object else {
// object doesn't exist anymore
self.viewToObjectObservation = nil
return
}

guard let value = change.newValue else { return }
object[keyPath: self.objectKeyPath] = value
}
}
}

但是我的模型的一些属性有类型 CustomEnum , CustomClass , Bool? , 和 ClosedRange<Int> ,并且要使用观察,我必须将它们标记为 @objc dynamic ,这产生了错误:
Property cannot be marked @objc because its type cannot be represented in Objective-C
方法二:使用 RxSwift rx.observe
我转向 RxSwift 和 rx.observe方法认为我可以解决这个问题,但同样的事情发生了(这次在运行时)。
// In some generic bridge class between the view and the model
func bind(to object: SomeObjectType) {
object.rx
.observe(SomeType.self, "someProperty")
.flatMap { Observable.from(optional: $0) }
.bind(to: self.controlProperty)
.disposed(by: disposeBag)
}

方法 3:使用 RxSwift BehaviorRelays?

这是我第一次使用 RxSwift,我知道我应该为我的模型使用 BehaviorRelay,但是我不想更改我的所有模型属性,因为我的模型对象正在使用其他框架。我可以尝试实现一个桥,将模型属性转换为 BehaviorRelay,但我会遇到同样的问题: 如何监听模型变化 .

In this question ,没有关于如何在不将所有模型属性重构为 RxSwift 的 Variable 的情况下监听属性更改的答案。 (目前已弃用)。

方法四:使用 didSet swift 属性(property)观察员?
didSetwillSet普通 Swift 中的属性观察器允许监听变化,但是这需要用这些观察器标记模型中的所有属性,我觉得这很不方便,因为我的模型对象有很多属性。如果有办法在运行时添加这些观察者,这将解决我的问题。

我相信我想要实现的目标很常见,有一组修改模型对象的 View ,但是我找不到将模型正确链接到 View 的方法,以便在需要时自动更新。

基本上,我正在寻找以下问题之一的答案:
  • 有没有我忽略的东西,有没有更好的方法来实现我想要的?
  • 或者如何克服“属性不能被标记@objc”的问题?
  • 或如何在不更改模型的情况下将我的模型对象桥接到 BehaviorRelay?
  • 或如何添加 didSet运行时的观察者?
  • 最佳答案

    你说:

    I believe that what I am trying to achieve is quite common, having a set of views that modify a model object, however I can't find a way to properly link the model to the view, so that both auto-update when needed.



    其实这根本不常见。您没有提到的一个想法是将整个模型包装到行为中继中。然后这组 View 可以修改您的模型对象。

    反过来,您的每个 View 都可以观察行为中继中的模型并相应地更新。例如,这是 Redux 模式的基础。

    您还可以使用您的方法 #3 并使用属性包装器使代码更简洁:
    @propertyWrapper
    struct RxPublished<Value> {
    private let relay: BehaviorRelay<Value>
    public init(wrappedValue: Value) {
    self.relay = BehaviorRelay(value: wrappedValue)
    }

    var wrappedValue: Value {
    get { relay.value }
    set { relay.accept(newValue) }
    }

    var projectedValue: Observable<Value> {
    relay.asObservable()
    }
    }

    但是要明白,你遇到这个问题的全部原因不是因为 Rx 本身,而是因为你试图混合范式。您正在增加代码的复杂性。希望这只是重构期间的临时增加。

    旧答案

    你说你想让它“以便 View 在其相应的模型属性更改时自动更新,并在控件更改时更新模型(双向绑定(bind))。”

    IMO,这种思考问题的方式是不正确的。最好是独立于所有其他输出检查每个输出并直接处理它。为了解释我的意思,我将使用将°F转换为°C并返回的示例......

    这听起来像是使用 2-way binding 的一个很好的理由,但让我们看看?
    // the chain of observables represents a view model
    celsiusTextField.rx.text // • this is the input view
    .orEmpty // • these next two convert
    .compactMap { Double($0) } // the view into an input model
    .map { $0 * 9 / 5 + 32 } // • this is the model
    .map { "\($0)" } // • this converts the model into a view
    .bind(to: fahrenheitTextField) // • this is the output view
    .disposed(by: disposeBag)

    fahrenheitTextField.rx.text
    .orEmpty
    .compactMap { Double($0) }
    .map { ($0 - 32) * 5 / 9 }
    .map { "\($0)" }
    .bind(to: celsiusTextField.rx.text)
    .disposed(by: disposeBag)

    上面的代码在没有双向绑定(bind)的情况下处理了文本字段之间的双向通信。它通过使用两个单独的 View 模型来做到这一点( View 模型是 text Observable 和 text Observer 之间的代码,如注释中所述。)

    我们可以看到很多重复。我们可以稍微干燥一下:
    extension ControlProperty where PropertyType == String? {
    func viewModel(model: @escaping (Double) -> Double) -> Observable<String> {
    orEmpty
    .compactMap { Double($0) }
    .map(model)
    .map { "\($0)" }
    }
    }

    您可能更喜欢与我上面使用的不同的错误处理策略。我力求简单,因为这是一个例子。

    但关键是每个可观察链都应该以特定效果为中心。它应该结合所有导致该效果的原因,在输入上实现某种逻辑,然后发出该效果所需的输出。如果您对每个输出单独执行此操作,您会发现根本不需要双向绑定(bind)。

    关于ios - 绑定(bind)模型和 View : how to observe object properties,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62378497/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com