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に引っかからないので注意が必要です。