如何引用一个值类型属性
在更新 Best Before 的过程中,我决定重写 ForwardStrategy
的部分。过去 ForwardStrategy
中不同的转发目标所储存的信息其实是一样的,只是不同的转发目标会隐藏其不包含的属性。因此其对应的表单也是这样设计的,表单中其实包含了所有属性对应的 cell,但是在选中某些目标时,其中一些 cell 被隐藏起来了。
举个例子吧:比如说我们有 Things 和 Mail 两种转发目标,当我们选中 Things 时,因为它并没有收件人,所以其对应的 cell 被隐藏起来了。当我们切换到 Mail,收件人就得以重见天日,但截止日期却被隐藏起来了。这个方案当然是 work 的,但是却有一个明显的缺点,一旦我们需要添加新的转发目标,或更新旧有的转发目标时出现了新的属性时,我们就需要修改模型,并在表单中增加新的 cell 了。
因此在这次更新中,我决定将转发目标的属性以 JSON 为格式储存,这也避免了不同转发目标混用属性的尴尬。表单方面也需要作出修改。窒息观察可以发现,这些属性有几个固定的类型:
1 | enum ForwardFormElement { |
我们只需要提供每一种转发目标的 [ForwardFormElement]
,就能依次利用 Form Builder 生成相应的表单了。同时也为了契合我在用的 Form Builder 的特点,我希望在生成每一个 Row
时能够通过它的 onChange
方法将变动绑定到转发目标的相关属性上。这才有了本篇文章的主题。
先梳理一下,我希望的结果是:
- 转发目标提供
[ForwardFormElement]
给 Form Builder - ForwardFormElement 中包含了一个对转发目标属性的弱引用
- Form Builder 生成
Row
时通过onChange
方法绑定属性
row.onChange { bind.val = $0 }
bind.val
改变时同时改变转发目标的相关属性(很可能是值类型)
那么接下来就是怎么实现 bind: PropertyRefTo<T>
的问题了。
很简单,Swift 4 给我们带来了 KeyPath subscription,只要我们持有转发目标的弱引用,然后再存对应的 KeyPath 就好了。
1 | class PropertyRefTo<V> { |
能用吗?能用。但是我还有一个额外的需求,有一些对应 case picker(title: String, options: [String], bind: PropertyRefTo<Int>)
的属性是 Enum,但 Swift 并没有 Generic Case 这样的东西,所以我们要让 PropertyRef
能够按照我们的要求将其 val
转换成我们需要的类型。显而易见,不提供 object
和 keyPath
,而是提供 val
的 getter 和 setter 不就好了吗。
1 | init(_ object: O, getter: @escaping (O)->V?, setter: @escaping (O, V)->Void) { |
我们在 block 里通过 capture list 弱引用 object
,因为这么写了,所以我们要求 O: AnyObject
。最后 Key Path 这种这么方便的东西,我们也可以提供一个 convenience initializer。
1 | convenience init(_ object: O, _ keyPath: ReferenceWritableKeyPath<O, V>) { |
至此我们就能获得一个 AnyObject
的非引用类型的属性的引用啦。
随后代码就可以写成
1 | .highlightTextView( |