SwiftChainingの解説記事その7です。
SwiftChainingとは何か?というのは、こちらの最初の記事を参考にしてください。
SwiftChainingのメモリ管理
SwiftChaining
でバインディング対象にしたオブジェクトは、監視を開始してもバインディング処理の中では強参照で保持されません。
バインディング対象のオブジェクトは内部的に弱参照で保持されているので、意図的にどこかしらのプロパティなどで保持していないとバインディングの処理を書いたそばから解放されてしまって何も動かないということが起こってしまいます。
SwiftChaining
のメモリ管理のポリシーとしては、「実装をし忘れて動かないことより、知らずに動き続ける方が複雑な問題を生む」という考えで極力バインディング部分で強参照しないようにしています。
とは言えUIControlAdapter
やNotificationAdapter
のような、本来の監視対象となるオブジェクトとの橋渡し的な役割のオブジェクトの場合、わざわざプロパティで保持するのも冗長です。
そこで、例外として送受信オブジェクトをObserver
側に強参照で保持させるという方法を用意しています。
Observerに監視対象のオブジェクトを保持させる
Chainable
に適合したクラスでは、SwiftChaining
の内部で保持させるためにretain
という関数が呼び出せるようになっています。
以前の記事に出てきたNotificationAdapter
を使うコードを例にすると、以下のようにchain()
の前にretain()
を書く感じになります。
let observer = NotificationAdapter(MyClass.name,
object: object,
notificationCenter: .default)
.retain()
.chain()
.map { (notification: Notification) -> String? in
return notification.userInfo?["key"] as? String
}
.sendTo(receiver)
.end()
これで、NotificationAdapter
はobserver
の内部に強参照で保持され、プロパティなどに保持する必要があるのはobserver
だけになります。
上記のコードでretain()
を挟まなかった場合には、chain()
の後のmap
を実行する時点で既にNotificationAdapter
は解放されてしまっています。chain()
はChain
クラスを返すので、NotificationAdapter
は必要なくなるからです。
受信対象のオブジェクトを保持させる
sendTo()
で受け取るReceivable
に適合したクラスでもretain()
が使えるので、また別の記事にあったKVOAdapter
でイベントの受信をするような場合でも、Adapterをプロパティで保持しなくても良くなります。
以下のようにsendTo()
に渡す前にretain()
を呼びます。
self.isOnHolder
.chain()
.sendTo(KVOAdapter(self.mySwitch, keyPath: \UISwitch.isOn ).retain())
.sync()
.addTo(self.pool)
retain
を呼ぶ、というのがちょっと昔に戻った感じがしないでもないですが、解放は自動で積極的に行われるようにしているので、メモリリークを気にしないといけないようなものではありません。
retainの書き忘れ
こういったretain()
のような記述はついつい書き忘れてしまいがちですが、バインディング処理の構築時にオブジェクトが解放されてしまっていないかどうかはAssert
をしているので、どの変数にも入れず記述している場合は気付くことができるはずです。
ただし、ローカル変数に代入してから扱う場合は、バインディング処理の構築中に解放されることはなくAssert
に引っかからないので注意が必要です。