注册

可变共享结构(第二部分)

我们改进了新数据类型的观察能力。

在上一章中,我们构建了一个名为的struct / class混合类型 Var。今天我们将继续实验。

Var类包含一个结构,我们可以利用关键路径寻找到的结构。如果我们有一个people内部的阵列Var,我们希望采取先Person出数组,那么我们得到另一个Var与 Person。更新它Person会修改原始数组,这样我们就会给出Var引用语义。但是如果我们需要的话,我们仍然可以获得结构的复制行为:我们可以将结构值取出Var并具有本地副本。

我们也深入观察。只要有任何变化,根变量就会知道它。我们仍然有一个有点笨拙的API,因为我们Var使用observe闭包初始化,这意味着我们只能在初始化时在根级别添加一个观察者。我们想用一种addObserver方法改进这个API,并且如果我们想要观察根结构或任何其他属性,请使用它。

添加观察者

我们从初始化程序中删除观察者闭包并设置一个新addObserver方法。因为我们将大量使用观察者闭包,所以我们可以为它创建一个类型别名:

final class Var {
// ...
init(initialValue: A) {
var value: A = initialValue {
didSet {

}
}
_get = { value }
_set = { newValue in value = newValue }
}

typealias Observer = (A) -> ()
func addObserver(_ observer: @escaping Observer) {

}
// ... }

以前,我们将一个观察者闭包连接到初始化器中的struct值,但现在我们无法访问那里的观察者。我们需要将所有观察者存储在一个地方,从一个空数组开始,然后连接观察者和结构值:

final class Var {
// ...
init(initialValue: A) {
var observers: [Observer] = []
var value: A = initialValue {
didSet {
for o in observers {
o(value)
}
}
}
_get = { value }
_set = { newValue in value = newValue }
}
// ... }

现在我们仍然需要一种方法来向数组中添加一个观察者。我们重申我们做与技巧get,并set与转addObserver成一个属性,而不是一个方法:

final class Var {
let addObserver: (_ observer: @escaping Observer) -> ()

// ...

init(initialValue: A) {
var observers: [Observer] = []
var value: A = initialValue {
didSet {
for o in observers {
o(value)
}
}
}
_get = { value }
_set = { newValue in value = newValue }
addObserver = { observer in observers.append(observer) }
}
// ... }

在我们可以使用之前addObserver,我们必须将它设置在我们的其他私有初始化程序中。为此,我们将从外部传入一个闭包,以便我们可以在下标实现中定义闭包:

fileprivate init(get: @escaping () -> A, set: @escaping (A) -> (), addObserver: @escaping (@escaping Observer) -> ()) {
_get = get
_set = set
self.addObserver = addObserver
}

在键路径下标中,我们现在必须定义一个addObserver 闭包,它接受一个观察者并用类型的值调用这个观察者 B。我们只有类型的值A,但我们也可以self在这个闭包中观察并使用关键路径来获取B收到的A:

subscript(keyPath: WritableKeyPath,>

无论我们嵌套我们Var的深度多少,观察者总是被添加到根Var,因为一个孩子Var通过观察者直到它到达observers根的数组Var。这意味着只要根值发生变化就会调用观察者 - 换句话说:即使属性本身未更改,也可能会调用特定属性的观察者。

我们还在addObserver集合的下标中传递了一个类似的闭包:

extension Var where A: MutableCollection {
subscript(index: A.Index) -> Var {
return Var(get: {
self.value[index]
}, set: { newValue in
self.value[index] = newValue
}, addObserver: { observer in
self.addObserver { newValue in
observer(newValue[index])
}
})
}
}

让我们看看这是如何工作的:

let peopleVar: Var<[Person]> = Var(initialValue: people)
peopleVar.addObserver { p in
print("peoplevar changed: \(p)")
}
let vc = PersonViewController(person: peopleVar[0])
vc.update()

这会将peopleVar更改打印到控制台。但是,我们现在也可以添加一个观察者到Var的PersonViewController,这将打印与新的一个额外的行Person值:

final class PersonViewController {
let person: Var

init(person: Var) {
self.person = person
self.person.addObserver { newPerson in
print(newPerson)
}
}

func update() {
person.value.last = "changed"
}
}

删除观察者

我们现在可以添加观察者,但我们无法删除它们。如果视图控制器因为其观察者仍然在那里而消失,这就成了问题。

我们可以采取类似于反应性图书馆工作方式的方法。添加观察者时,将返回不透明对象。通过保持对该对象的引用,我们保持观察者活着。当我们丢弃对象时,观察者将被删除。

我们使用一个名为的辅助类,Disposable它接受一个在对象取消时调用的dispose函数:

final class Disposable {
private let dispose: () -> ()
init(_ dispose: @escaping () -> ()) {
self.dispose = dispose
}
deinit {
dispose()
}
}

我们更新签名addObserver返回Disposable:

final class Var {
private let _get: () -> A
private let _set: (A) -> ()
let addObserver: (_ observer: @escaping Observer) -> Disposable

// ... }

如果我们想要删除观察者,我们必须改变观察者商店的数据结构。数组不再有效,因为无法比较函数以找到要删除的数组。相反,我们可以使用由唯一整数键入的字典:

final class Var {
// ...

init(initialValue: A) {
var observers: [Int:Observer] = [:]
// ...
}
// ... }

生成这些整数的一种很酷的方法是使用Swift的惰性集合。我们创建一个范围从0到无穷大的迭代器,每次我们需要一个id时,我们可以调用next()这个迭代器。这返回一个可选项,但是我们可以强制解包它,因为我们知道它不能是nil:

final class Var {
// ...

init(initialValue: A) {
var observers: [Int:Observer] = [:]
// ...
var freshInt = (0...).makeIterator()
addObserver = { observer in
let id = freshInt.next()!
observers[id] = observer
// ...
}
}
// ... }

我们现在将观察者存储在字典中。当我们不再使用它们时,剩下要做的就是丢弃观察者。我们返回一个Disposable 带有dispose函数的函数,该函数从字典中删除观察者:

final class Var {
// ...

init(initialValue: A) {
var observers: [Int:Observer] = [:]
// ...
var freshInt = (0...).makeIterator()
addObserver = { observer in
let id = freshInt.next()!
observers[id] = observer
return Disposable { observers[id] = nil }
}
}
// ... }

最后,我们必须addObserver在私有初始化程序中修复签名,它仍然声明返回void而不是Disposable:

fileprivate init(get: @escaping () -> A, set: @escaping (A) -> (), addObserver: @escaping (@escaping Observer) -> Disposable) { /*...*/ }

现在我们的代码再次编译,但是我们确实得到了一个编译器警告我们忽略Disposable了视图控制器中返回的事实。这就解释了为什么我们不再使用更改的Person值获取print语句,因为我们应该保留对观察者的引用以 Disposable使其保持活动状态:

final class PersonViewController {
let person: Var
let disposable: Any?

init(person: Var) {
self.person = person
disposable = self.person.addObserver { newPerson in
print(newPerson)
}
}

func update() {
person.value.last = "changed"
}
}

我们现在保留观察者,我们在更新后得到了print语句Person。重申这里发生的事情:视图控制器被释放的那一刻,它的属性被清除,Disposabledeinits,并且这通过将其id设置为来调用将观察者带出字典的代码nil。

注意:如果我们想self在观察者中使用,我们必须使它成为弱引用,以避免创建引用循环。

比较新旧价值观

我们实现的一个重要方面是观察者不仅在观察到的Var变化时触发,而且在整个数据结构发生任何变化时触发。

如果PersonViewController想要确定它已经Person 改变了,它应该能够将新值与旧值进行比较。因此,我们将更改Observer类型别名以提供新值和旧值

typealias Observer = (A, A) -> ()

这意味着使用新版本和旧版本调用观察者 A。为了明确这一点,我们应该将值包装在一个结构中,并在两个字段中描述它们是什么,但我们正在跳过该部分。

我们在里面调用观察者的地方Var,我们现在也必须传递旧值:

init(initialValue: A) {
// ...
var value: A = initialValue {
didSet {
for o in observers.values {
o(value, oldValue)
}
}
}
// ... }

subscript(keyPath: WritableKeyPath,>

而在MutableCollection标,我们也应该通过旧值观察员:

extension Var where A: MutableCollection {
subscript(index: A.Index) -> Var {
return Var(get: {
self.value[index]
}, set: { newValue in
self.value[index] = newValue
}, addObserver: { observer in
self.addObserver { newValue, oldValue in
observer(newValue[index], oldValue[index])
}
})
}
}

观察者PersonViewController可以比较新旧版本,看看它的模型是否确实改变了:

final class PersonViewController {
// ...
init(person: Var) {
self.person = person
disposable = self.person.addObserver { newPerson, oldPerson in
guard newPerson != oldPerson else { return }
print(newPerson)
}
}
// ... }

最后,我们需要修复观察者peopleVar:

peopleVar.addObserver { newPeople, oldPeople in
print("peoplevar changed: \(newPeople)")
}

通过对视图控制器中的人进行更改,我们测试视图控制器的观察者忽略它:

peopleVar[1].value.first = "Test"

讨论

我们将反应式编程与观察变化的能力和面向对象的编程结合起来。

在这段代码中还有一个令人惊讶的等待。我们将第一个Person从数组移交给视图控制器。如果我们然后删除people数组的第一个元素,视图控制器突然有一个不同的 Person:

peopleVar.value.removeFirst()

的第一个元素是从阵列中删除,但Var在 PersonViewController仍然指向peopleVar[0]因为我们使用一个动态评估标。在大多数情况下,这是不希望的行为。一个可以改善这种行为的例子是有一个first(where:)允许我们通过标识符选择元素的方法。

到目前为止,我们对我们的建设感到兴奋。也许它可能改变我们编写应用程序的方式。或者,它可能仍然太实验性:我们设法编译代码,但我们不确定该技术将在何处以及如何破解。

即使我们不在Var实践中使用,我们也结合了很多有趣的功能,这些功能可以很好地展示Swift的强大功能:泛型,关键路径,闭包,变量捕获,协议和扩展。

将来,尝试只部分应用方面可能会很酷Var。假设我们有一个数据库接口,它从数据库中读取一个人模型并将其作为a返回Var。我们可以使用它自动将结构的更改保存回数据库。似乎会有像这样的例子,其中Var技术可能是有用的。

转自:https://www.jianshu.com/p/20030b35e11c

0 个评论

要回复文章请先登录注册