=====================
色々面倒なdelegate(callback)をBlockで記述できるBlocksKit。これを実装する方法を考えます。
実はBlocksKitを使ったことがないため色々違いはあると思われますが、 各種callbackをBlockで受け取れるようにする というのが本題です。
目標
これらを実装することを目的としています。
- delegateをBlockで書かえるようにする。標準SDKのクラスもそれ以外も、プロトコルのメソッドをcallbackするタイプ全てをBlockで書けるようなものを作る
- SDKのよく使うクラス(UIAlertViewやUITextField等)のdelegateをBlockで簡単に書けるようにする
- UIControl, UIGestureRecognizer, KVO, NSTimer, target/selecterをBlockで書けるようにする
作り方を考える
各種callbackはどれもインスタンスを登録しメソッドを呼び出すような形になっています。この部分を使わざるをえません。そこで通常のcallbackの受け取り方で受け取るオブジェクトを間に挟み、そのオブジェクトからBlockを呼び出すような仕組みを考えます。
ひとつは単純にオブジェクトでcallbackを受け取り、プロパティで保持しておいたBlockを呼び出す方法です。もうひとつは動的にメソッドを実装する方法です。callbackを受け取るメソッドを動的に実装し、そのメソッドからBlockが呼び出されるようにします。
どちらの場合も問題となるのが、そのオブジェクトをどこで管理するか? ということです。callbackを受け取るBlockを生成するところ(大抵はインスタンス)で管理する方法が思い浮かぶかもしれませんが、これだと少々面倒です。callbackの通知元と通知先の生存期間が異なるため、片方が無効になっても不正な動きをしないよう対策が必要となります。
そこで callbackの通知元自身にそれらのオブジェクトを管理(保持)させてしまいます。こうすると、callbackの通知元と通知先の生存期間が同じになり、通知元インスタンスが解放されそれ以上callbackされなくなるタイミングで、そのオブジェクト(と関連するBlock)も解放されます。
最後になってしまいましたが、これらの機能を実装するために callbackの通知元を継承して拡張するようなことはしたくありません。もちろんそうしてしまったほうが実装は簡単ですが、NSTimerならまだしもKVOのためにNSObjectを継承したクラスを作るのは非現実的です。カテゴリを使えば既存のクラスを変更せずメソッドを追加できます。カテゴリではインスタンス変数を追加できませんが、そこはAssociatedObjectの機能を使って解決します。
作り方
全部をひとつの記事にするとかなり長くなってしまうため、いくつかの記事にわけます。
-
既存クラスを継承せず、そのインスタンスにオブジェクトを保持する機能が必要となります。runtimeのAssociatedObjectという機能を使うと実現可能です。
-
メソッドを動的に実装し、そのメソッドが呼ばれた時にBlockを実行するようなクラスが必要です。これもruntimeの機能で実現可能です。
-
2 のメソッドの動的な実装をより正確に説明すると、「classに対してメソッドを追加する」となります。インスタンスにメソッドが追加されるわけではありません。よって何も考えず動的にメソッドを実装した場合、ありとあらゆる 2 のクラスのインスタンスがそのメソッドを実装していることになってしまいます。今回の場合それだと使いにくいため、自動で動的にサブクラスを登録しインスタンスを生成するようにします。このサブクラスにメソッドを追加すれば上記問題を解決できます。これもruntimeの機能で実現可能です。
-
様々なdelegate(プロトコルで定義されたメソッドをcallbackするもの)をBlockで書くための汎用的なクラスを作成します。またcallbackの通知元にその汎用クラスのインスタンスを管理(保持)させるため、カテゴリで既存のクラスを拡張します。。
-
4 では少々冗長な記述になってしまう部分を、ある特定のクラスのdelegateに限定することで簡単に書けるようにします。カテゴリを使ってUIAlertView等を拡張します。
-
UIControl等、標準のクラスでcallbackがあり、よく使うものをBlockで書けるようにカテゴリを使って拡張します。
完成品
既に実装したものが以下にあります。「使えればいいよ」「解説いらないよコード見れれば」という場合はこちらをどうぞ。
メイン部分
https://github.com/siagency/SIATools/tree/master/SIATools/Blocks
NSObject+SIAToolsやSIAIDも使っています
https://github.com/siagency/SIATools/tree/master/SIATools/Extensions