ReactiveSwift
工作时,如果不遵循简单的规则,你真的会很容易掉入浪费资源的坑里。
用完即丢弃 (Disposables)
在使用 ReactiveSwift
工作时,可帮助我们处理内存管理的基本单元是 disposable
。在你开始观察 Signal
的同时,或启动任何与 Signal Producer
相关的工作时,你将可以访问 Disposable
。如果你对该 Signal
的执行结果不感兴趣,则可以在该 disposable
中简单地调用 .dispose()
方法,并且不再接收任何更新。这也意味着,只要 SignalProducer
注意到没有人对它的结果感兴趣,它就可以停止自己的工作并清理资源。
在退出应用程序的一个屏幕时,通常要释放资源。这就意味着,你也应该对所有的 disposables 进行处置。当然了,要把每一个 disposable 都存到单独的变量中然后在你对更新它们再也不感兴趣的时候处理掉是比较艰难的。这就是为什么我们要对 disposables - CompositeDisposable 使用这样的一个容器 。基本上你只要把 disposable 扔到这个容器里面,然后在你的视图控制器去初始化的时候把它们全部处理掉就行了。
下面让我们来看看使用 disposables 是如何运作的。
// public variable accessible from outside of class
var producer: SignalProducer<String, NoError>
init() {
producer = SignalProducer {[weak self] observer, compositeDisposable in
guard let strongSelf = self else { return }
compositeDisposable.add {
print("I've been disposed! I can clean my resources ;)")
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
if !compositeDisposable.isDisposed {
strongSelf.performHeavyCalculation()
observer.send(value: "1")
}
})
DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: {
if !compositeDisposable.isDisposed {
strongSelf.performHeavyCalculation()
observer.send(value: "2")
}
})
}
// If you have compositeDisposables variable, then you can add it there
// disposables += producer.startWithValues ...
// You keep received disposable in variable
let disposable = producer.startWithValues {[unowned self] (value) in
print(value)
self.performHeavyCalculation()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
disposable.dispose() // After some time, you are not interested in producer's work, and you kindly tell him that
}
}
这里发生了什么呢? 生产者要一直等到 startWithValues 被调用的时候才会运行。在此之后,我们得按计划行事,接收发送给我们的值1和2。它们也会执行一些重度的计算。两秒之后,我决定不再关注结果,因此对接收到的 disposable 进行了处理,并且再也不去接收 startWithValues 块中发出的更新了。然而,生产者中的工作已经在计划好了。这就是为什么我要放置一个if语句来检查是不是有仍然对生产者的工作感兴趣的。如果没有的话,我就不会去执行了。
var disposables = CompositeDisposable()
disposables += viewModel.criticalInfo.observeValues {[unowned self] (value) in
// react to value
}
deinit {
disposables.dispose()
}
在此例中,让我们设想一下,如果你在对类进行初始化的时候创建了 disposables 变量,那么在你开始对信号进行观察的时候,就要将每一个 disposable 都添加到你的容器中。你可以在任意的时间将它们处理掉,不过通常情况下,你会在对容器解除分配的时候做这件事情,因此你可以把这段代码添加到 deinit。
你可能已经注意到了,有些部分使用了 [weak self] 以及 [unowned self]。让我们来仔细看看这个!
使用闭包
Disposables 是引导你进行内存管理天堂的重要项。在使用 ReactiveSwift 时,你必须记住的下一件事是管理传递给观察者的闭包中的关系。当你在这样的闭包中使用 self
变量时,你创建一个 retain 循环,因为你保存了对 self
的强引用。控制器持有一个闭合引用,而且闭合也持有一个控制器的引用。它们不能在任何时候被释放掉。为了对 self
有一个弱引用,你可以添加 [weak self]
或 [unowned self]
给这样的闭包。如果你不添加下列语句中的其中之一,则 deinit
方法中的 disposables.dispose()
将不会被访问,因此控制器将不会被反初始化。
// weak reference, but we bet that self will not be nil
disposables += signal.observe {[unowned self] values in
self.workWithMeAllTheTime()
}
// weak reference, but self becomes optional
disposables += signal.observe {[weak self] values in
guard let strongSelf = self else { return }
strongSelf.workWithMeAllTheTime()
}
...
deinit {
disposable.dispose()
}
你所问到的 [weak self] 和 [unowned self] 有什么区别呢? 当你使用 [weak self],你会想到闭包,也有可能在某些时候 self 可能是零。 我通常在这种闭包的开始放一个 let 语句的守护量,所以如果 self 是零的话,我不会继续做任何操作。 另一方面,也有 像[unowned self] 这种不会通知我们 self 在某种情况下是否为零。这就需要我们负责,留心确保如果 self 被反初始化之后,这个块就不会被调用了。如果你妥善处理这些可自由支配的逻辑,最常见的是 [unowned self] 是一个安全的赌注,因为这些闭包不会在 self 反初始化之后执行的。第一个例子的附加说明
让我们回看下第一个例子中的代码。你可以看到,对于 SignalProducer
,我使用了 [weak self];
对于观察者,我使用了 [unowned self]
。我为什么要这样做呢?当我从 startWithValues
闭包中开始观察生产者的值时,我非常确定在控制器 deinit 时我会调用 dispose
,所以我知道如果我需要 self
,它就会在那里。涉及到 SignalProducer
可能有点不一样,从外面是可以访问它的。试想一下,当这个类还存在时我就保存了 producer 对象,并在它被反初始化之后启动其工作。如果我在那时拥有 [unowned self]
对象,那么就会引起崩溃。只要我在 producer 启动之时拥有 [weak self]
对象,我可以检查 self
是否存在,如果不存在的话,我会终止任何其他工作。如果存在的话,我会创建一个对 self
的引用,并继续我的工作。
总会存在边界情况可能会导致在 unowned 和 weak 之间的选择难题,但随着时间演进,你会发现与他们一起工作会将变得愈加容易!下次见!