Objective-C オブジェクト間通信のパターン


この記事は、Tech Women Advent Calendar 2014の17日目の記事です。

基本ですが、あらためてiOS開発でのオブジェクト間通信についてまとめてみました。

オブジェクト間通信

Objective-Cでソースコードを書いているときに、オブジェクト同士で通信を行う必要は頻繁に発生します。

通信元のオブジェクトが通信先のポインタを持っている場合もあるし、ない場合もあるし。
通信元、通信先のどちらのオブジェクトも動的に生成されたり消えたりするし。
複数の相手に通信したい場合もあるし、一つだけでいい場合もあるし。
いろいろな状況に応じてこのオブジェクト間通信を使い分ける必要が有ります。

「呼び出し元」と「呼び出し先」だとわかりにくいので、AliceがBobやCarolをよぶ、(AliceからBobやCarolにメッセージを送る)としてみましょう。

Target-Action

まず最初はTarget-Action。
UIControlクラスのイベント処理でよく使われます。

targetがnilの場合には、Responder Chainにそってイベントが送られていきます。

// 送信側
- (void)viewDidLoad
{
    [super viewDidLoad];
   
  // ボタンを作成。
  UIButton* A = [UIButton buttonWithType:UIButtonTypeRoundedRect];
  A.frame = CGRectMake(0, 0, 100, 50);
  [self.view addSubview:A];

  // Target Actionを設定
  [A addTarget:B action:@selector(sayHello:) forControlEvents:UIControlEventTouchUpInside];
  [A addTarget:C action:@selector(sayGoodbye:) forControlEvents:UIControlEventTouchUpInside];
 
  // actionsの確認
  NSArray* appleActions = [buttonTarget actionsForTarget:B forControlEvent:UIControlEventTouchUpInside];
  NSArray* mikanActions = [buttonTarget actionsForTarget:C forControlEvent:UIControlEventTouchUpInside];

  // actionのremove
//  [A removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside];
 
  // next responderの取得
//  id next = [A nextResponder];

}
// 受信側
@implementation Bob
-(void)sayHello:(id)sender{
  NSLog("say hello.");
}

Notification

Notification(通知)は、送信オブジェクトと受信オブジェクトが直接のつながりがなくても通信可能な方法です。
そのため、Notificationを使用すると、他のオブジェクト間通信より全体的な処理の流れが理解しづらくなってしまうことがあります。
個人的には、使う場合には注意しています。

アプリの画面状態にかかわらず受送信が必要な情報な場合によく使われます。
システムの状態変化の通知にも使われています。

// 送信側
@implementation Bob
- (void)sendNotification
{
// 受信側
@implementation Carol
- (void)sendNotification
{
  // App Notificationを取得
  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(listenToMe:) name:@"ToyshipNotification" object:nil];

  // System Notificationを取得
  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changePlayerStatus:) name:MPMusicPlayerControllerVolumeDidChangeNotification object:nil];

  [[MPMusicPlayerController iPodMusicPlayer] beginGeneratingPlaybackNotifications];

}

 
  // App Notificationを取得
  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(listenToMe:) name:@"ToyshipNotification" object:nil];

  // System Notificationを取得
  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changePlayerStatus:) name:MPMusicPlayerControllerVolumeDidChangeNotification object:nil];

  [[MPMusicPlayerController iPodMusicPlayer] beginGeneratingPlaybackNotifications];

}

Key-Value Observation

Key-Value Observatoin(KVO・キー値監視)とは、あるオブジェクトのキーに関連づけられた値を監視する仕組みです。
呼び出し先は呼び出し元に対してキーを指定して登録すると、値が変わった時に通信されるようになります。

ただし、removeをしないとcarshの原因となることがあり、個人的には注意して使っています。

- (void)viewDidLoad
{
    [super viewDidLoad];

  // keySliderのObservationを設定。
  [self.keySlider addObserver:self forKeyPath:@"value" options:NSKeyValueObservingOptionNew context:nil];
 
  // segmentControlのObservationを設定。
  [self.keySegment addObserver:self forKeyPath:@"selectedSegmentIndex" options:NSKeyValueObservingOptionNew context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
 
  if( [NSStringFromClass([object class]) isEqualToString:@"UISlider"]){
    UISlider* slider = object;
    NSLog(@"  -- observe with %@ %f",keyPath,slider.value);
    self.sliderValue.text = [NSString stringWithFormat:@"%1.2f",slider.value];

  }
  else if( [NSStringFromClass([object class]) isEqualToString:@"UISegmentedControl"]){
    UISegmentedControl* segment = object;
    NSInteger selection = segment.selectedSegmentIndex;
    self.segmentValue.text = [NSString stringWithFormat:@"%ld",(long)selection];
   
  }
 
}

キー値コーディングは、TableViewCellの表示変更などによく使われており、こんな感じで実装します。

//キー値コーディングを使用しないデータソースメソッドの実装
- (id)tableView:(NSTableView *)tv objectValueForTableColumn:(id)column row:(int)row {
    Employee *emp = [employees objectAtIndex:row];
    if ( [[column identifier] isEqualToString:@”name”] ) {
        return [emp name];
    }
    if ( [[column identifier] isEqualToString:@”dept”] ) {
        return [emp department];
    }
    if ( [[column identifier] isEqualToString:@”title”] ) {
        // etc...
    }
    // etc...
}
//キー値コーディングを使用したデータソースメソッドの実装
- (id)tableView:(NSTableView *)tv objectValueForTableColumn:(id)column row:(int)row {
    Employee *emp = [employees objectAtIndex:row];
    return [emp valueForKey:[column identifier]];
}

Delegate

Delegate(移譲)はObjective-Cの言語構造に依存しない汎用的なデザインパターンです。
iOSでは、UIKitを中心によく使われるオブジェクト間通信の仕組みです。

UITableViewControllerなど、iOSではよく使われるパターンですが、一対一の関係になるため、多く相手に通信することはできません。

まとめ

以上の4つがObjective-Cでよく使われるオブジェクト間通信です。
それぞれに利点・欠点があるので、適切に使い分けていきたいですね。

Xcode 通知元 通知対象
Target-Action なんでも 一対多
Notification なんでも 一対多
Key-Value Observation なんでも 一対多
Delegate なんでも 一対一