19

質問

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 ライブラリよりも高レベルのシグナル パターンが提供され、ブロックベースのイベント ハンドラーやクラス レベルのメソッドよりも優れた状態をシグナル ハンドラー内にカプセル化できます。

4

4 に答える 4

7

個人的には、デリゲートを使うのは嫌いです。Objective-Cの構造が原因で、コードが煩雑になります。別のオブジェクトを作成したり、イベントの1つを通知するためだけにプロトコルを追加したりする必要があり、5/6を実装する必要があります。このため、私はブロックを好みます。

それら(ブロック)には欠点がありますが(たとえば、メモリ管理には注意が必要です)。それらは簡単に拡張でき、実装が簡単で、ほとんどの状況で意味があります。

Appleの設計構造では、sender-delegateメソッドを使用できますが、これは下位互換性のためだけです。最近のAppleAPIは、Objective-cの将来であるため、ブロック(CoreDataなど)を使用しています。船外で使用するとコードが乱雑になる可能性がありますが、ObjectiveCでは不可能なより単純な「匿名のデリゲート」も可能になります。

しかし、結局のところ、それは本当にこれに要約されます。ブロックとデリゲートを使用する代わりに、古い、より古いプラットフォームを放棄する用意がありますか?デリゲートの主な利点の1つは、objc-runtimeのどのバージョンでも機能することが保証されているのに対し、ブロックは言語に最近追加されたものです。

NSNotificationCenter/に関する限りKVO、これらは両方とも有用であり、目的がありますが、代理人として使用することを意図したものではありません。どちらも結果を送信者に送り返すことはできません。状況によっては、それが不可欠です(-webView:shouldLoadRequest:たとえば)。

于 2012-06-13T21:28:56.713 に答える
1

正しいことは、両方を実装し、それをクライアントとして使用し、何が最も自然に感じられるかを確認することだと思います。どちらのアプローチにも利点があり、コンテキストと SDK の使用方法に大きく依存します。

セレクターの主な利点は、単純なメモリ管理です。クライアントが正しく登録および登録解除する限り、メモリ リークを心配する必要はありません。ブロックを使用すると、クライアントがブロック内で何をするかによって、メモリ管理が複雑になる可能性があります。コールバック メソッドの単体テストも簡単です。ブロックは確かに testable になるように書くことができますが、私が見た限りでは一般的ではありません。

ブロックの主な利点は柔軟性です。クライアントはローカル変数を ivar にせずに簡単に参照できます。

そのため、ユースケースに依存していると思います。そのような一般的な設計上の質問に対する「客観的な正解」はありません。

于 2012-06-14T18:01:04.520 に答える
1

素晴らしい書き込み!

個人的な意見では、多くの JavaScript を記述した結果、イベント駆動型プログラミングは、デリゲートをやり取りするよりもはるかにクリーンに感じられます。

リスナーのメモリ管理の側面に関して、これを解決しようとする私の試み (Mike Ash のMAKVONotificationCenterから大きく引用) は、両方の方法でリスナーを安全に削除するために、呼び出し元とエミッターの両方のdealloc実装 (ここで見られるように) を切り替えます。

このアプローチがどれほど安全かは完全にはわかりませんが、壊れるまで試してみることをお勧めします。

于 2013-04-15T14:02:51.597 に答える
0

ライブラリに関することは、それがどのように使用されるかをある程度予測することしかできないということです。そのため、できるだけシンプルでオープンで、ユーザーになじみのあるソリューションを提供する必要があります。

  • 私にとって、これはすべて委任に最も適しています。リスナー (デリゲート) でのみ持つことができるのは正しいですが、これは制限がないことを意味します。ユーザーは、必要なすべてのリスナーを認識して通知するデリゲートとしてクラスを作成できるためです。もちろん、登録クラスを提供できます。これにより、登録されたすべてのオブジェクトでデリゲート メソッドが呼び出されます。
  • ブロックも同様です。
  • あなたがセレクターと呼んでいるものは、ターゲット/アクションと呼ばれ、シンプルでありながら強力です。
  • KVO は、カプセル化を弱めたり、ライブラリのクラスの使用方法に関する間違ったメンタル モデルにつながる可能性があるため、私にとっても最適なソリューションではないようです。
  • NSNotifications は、特定のイベントについて通知するのに便利ですが、非常に非公式であるため、ユーザーに使用を強制するべきではありません。誰かが同調しているかどうか、あなたのクラスは知ることができません。

API 設計に関するいくつかの有用な考え: http://mattgemmell.com/2012/05/24/api-design/

于 2012-06-13T21:19:40.347 に答える