質問
Objective-C でEventEmitter にインスパイアされたカスタム メッセージ システムを開発しています。リスナーがコールバックを提供するために、ブロックまたはセレクターを必要とする必要がありますか? また、その理由は?
サードパーティのライブラリを使用する開発者として、どちらを使用しますか? Apple の軌跡、ガイドライン、慣行と最も一致していると思われるのはどれですか?
バックグラウンド
私たちは、Objective-C でまったく新しい iOS SDK を開発しています。これは、他のサードパーティがアプリに機能を埋め込むために使用します。SDK の大部分は、リスナーへのイベントの通信を必要とします。
私が知っている Objective-C でのコールバックのパターンは 5 つありますが、そのうちの 3 つは当てはまりません。
- NSNotificationCenter - オブザーバーに通知される順序が保証されず、オブザーバーが他のオブザーバーがイベントを受信するのを防ぐ方法がないため (
stopPropagation()
JavaScript のように) 使用できません。 - Key-Value Observing - 私たちが実際に持っているのはメッセージの受け渡しであり、常に「状態」にバインドされているわけではないため、適切なアーキテクチャに適合していないようです。
- デリゲートとデータ ソース- 私たちの場合、デリゲートと呼べる単一のリスナーではなく、通常、多数のリスナーが存在します。
そのうちの 2 つが候補です。
- セレクター- このモデルでは、呼び出し元は、イベントを処理するためにまとめて呼び出されるセレクターとターゲットを提供します。
- ブロック- iOS 4 で導入されたブロックにより、オブザーバー/セレクター パターンのようなオブジェクトにバインドされることなく機能を渡すことができます。
これは難解な意見の質問のように思えるかもしれませんが、Objective-C の経験が浅すぎて判断できない客観的な「正しい」答えがあると感じています。この質問のためのより良い StackExchange サイトがある場合は、そこに移動してください。
更新 #1 — 2013 年 4 月
イベント ハンドラのコールバックを指定する手段として、ブロックを選択しました。この選択にはおおむね満足しており、ブロックベースのリスナー サポートを削除する予定はありません。ただし、メモリ管理と設計インピーダンスという 2 つの顕著な欠点がありました。
メモリ管理
ブロックは、スタック上で最も簡単に使用できます。ヒープにコピーして長寿命のブロックを作成すると、興味深いメモリ管理の問題が発生します。
含まれているオブジェクトのメソッドを呼び出すブロックは、暗黙的にself
の参照カウントを増やします。name
クラスのプロパティのセッターがあるとしますname = @"foo"
。ブロック内で呼び出すと、コンパイラーはこれを扱い、ブロックが存在している間は割り当てが解除されないように[self setName:@"foo"]
保持します。self
EventEmitter を実装するということは、長寿命のブロックを持つことを意味します。暗黙的な保持を防ぐには、エミッターのユーザーは、ブロックの外側への__block
参照を作成する必要があります。例:self
__block *YourClass this = self;
[emitter on:@"eventName" callBlock:...
[this setName:@"foo"];...
}];
このアプローチの唯一の問題はthis
、ハンドラーが呼び出される前に割り当てが解除される可能性があることです。そのため、ユーザーは、割り当てを解除するときにリスナーを登録解除する必要があります。
設計インピーダンス
経験豊富な Objective-C 開発者は、使い慣れたパターンを使用してライブラリを操作することを期待しています。デリゲートは非常によく知られているパターンであるため、正規の開発者はデリゲートを使用することを期待しています。
さいわい、デリゲート パターンとブロック ベースのリスナーは相互に排他的ではありません。エミッターは多くの場所からのリスナーを処理できる必要がありますが (単一のデリゲートでは機能しません)、開発者がクラスがデリゲートであるかのようにエミッターとやり取りできるようにするインターフェイスを公開することはできます。
これはまだ実装していませんが、ユーザーからのリクエストに基づいて実装する予定です。
更新 #2 — 2013 年 10 月
私は、この疑問を生んだプロジェクトにはもう取り組んでおらず、非常に満足して私の母国である JavaScript に戻ってきました。
このプロジェクトを引き継いだ賢明な開発者は、カスタム ブロック ベースの EventEmitter を完全に廃止するという適切な決定を下しました。次のリリースはReactiveCocoaに切り替わりました。
これにより、以前の EventEmitter ライブラリよりも高レベルのシグナル パターンが提供され、ブロックベースのイベント ハンドラーやクラス レベルのメソッドよりも優れた状態をシグナル ハンドラー内にカプセル化できます。