[iOS] Responder Chain と UIViewController

2011年6月26日日曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

@novi_ さんの指摘をきっかけに UIViewController が UIResponder のサブクラスで Responder Chain に参加していることを知った(今更だが。。)。

Responder Chain


Responder Chain とは UIResponderオブジェクトの連なり(リンク)のことで、イベント処理の順番を決定するのに使われる。イベントが発生すると、システムは Redponder Chain の順番にしたがって UIResponderオブジェクトを評価する。もし最初の UIResponderオブジェクトでイベント処理が行われない場合は Responder Chain の次の UIResponderオブジェクト へと移り再び評価を行う。イベント処理を行う UIResponderオブジェクトが見つかるまで Responder Chain 上のリンクを辿って評価を続けていく。一旦どこかの UIResponderオブジェクトがイベントを処理するとイベントの伝搬はそこで終わる。Responder Chain 上のどの UIResponderオブジェクトも処理を行わない場合はやがては最終的に UIApplication へ到達し、そこでも処理されない場合はそのイベントは処理されず無視される。

例えばボタンがタップされた場合、タップイベントを最初に処理するのは当該ボタンだが、そこでイベント処理が行われない場合、そのボタンが配置されている superview が評価される。もし superview にイベント処理コードが存在すれば、そこで処理が実行されてイベントの伝搬は終わる。もしイベント処理コードが superview上に無ければ、その次の UIWindow, UIApplication と Responder Chain が次々と辿られる。
なお明示的に Responder Chain を利用する場合はイベント登録時のターゲットに nil を渡す。
- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.button addTarget:nil 
                    action:@selector(touchMe:)
          forControlEvents:UIControlEventTouchUpInside];
}
これを nilターゲットと呼ぶ(動作も含めてたしか「nilターゲットアクション」と呼んだりもした)。この仕組は Mac OS X 由来で昔からある(Next由来か)。


Responder Chain と UIViewController


UIViewController は紐づいている UIView が参加している Responder Chain に割り込んで参加している。
UIResponder のサブクラスなのはこの Responder Chain に参加させる為だと思われる。nilターゲットをうまく使うことで紐づいている UIView 上のイベントをすべて UIViewController で集約的に処理することが可能になる。IBActionを宣言してXib上で接続する手間もない(そっちはそっちで明示的にターゲット・アクションの関係がわかりやすいくなるので良い面もあるが)。


※上の図は下記ページより借用
Event Handling Guide for iOS: Event Types and Delivery


サンプル


Responder Chain の確認と nilターゲットアクションが確認できるサンプルプログラムを作ってみた。
"display" ボタンを押すと、そのボタンが参加している Responder Chain をログに出力する。
- (void)_displayResponderChain:(UIResponder*)responder
{
    if (responder != nil) {
        NSLog(@"%@", responder);
        [self _displayResponderChain:[responder nextResponder]];
    }
}
- (IBAction)display:(id)sender {
    [self _displayResponderChain:sender];
    
}
再帰的に Responder Chain を辿って表示させる。結果はこんな感じ。
<UIRoundedRectButton: 0x4e13: 0x4e13c70>
 <UIView: 0x4e12f40;>
 <ResponderChainStudyViewController>
 <UIWindow: 0x4b44b30>
 <UIApplication: 0x4e012b0>
イベントの発生源であるボタンを起点に→superview→UIViewController→UIWindow→UIApplication と連なっているのがわかる。UIViewController は UIView と UIWindow の間に入り込んでいる。

次に nilターゲットアクション。
- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.button2 addTarget:nil 
                 action:@selector(touchMe:)
       forControlEvents:UIControlEventTouchUpInside];
}
としておき、UIViewController にイベントハンドラを書いておく。
- (void)touchMe:(id)sender
{
    NSLog(@"%s|%@", __PRETTY_FUNCTION__, sender);
    
}
すると "touch me !"ボタンが押されたときにそのイベントハンドラが呼び出され、Responder Chain が辿られていることがわかる。


ソースコード


GitHub からどうぞ。
ResponderChainStudy at 2011-06-26 from xcatsan/iOS-Sample-Code - GitHub


参考情報


Event Handling Guide for iOS: Event Types and Delivery

Responder Chain の解説ならびに UIViewController との関係が説明されている。

Responses

Leave a Response

人気の投稿(過去 30日間)