iPhoneアプリ作成までの道のり12

12. NSNotificationCenter

最後にNSNotificationCenterを紹介する。プロトコルデリゲートパターンは主に1対1、KVOは1対1〜1対Nに適用するものであるが、NSNotificationCenterを使うと、N対Nのイベント伝達を疎結合に実現できる。ただし、通知センターを使う方法は、他の2つの方法よりもオーバーヘッドを伴うことになる。しかし、うまく活用すれば、ある場面では他の2つの方法よりもパフォーマンスを向上させる可能性も持っている。

N対Nとは言っても、それは別にプロトコルデリゲートパターンでも実現可能なことである。複数のプロトコルを定義して、複数のプロトコルデリゲートを所持する送信元と、複数のプロトコルを実装するデリゲートを用意すれば、それでN対Nだ。通知センターを使えば、それをもっとスマートに、疎結合に実現できるという話である。

通知センターは、N対Nが可能であること以外に2つのメリットを提供する。

①非同期処理POSTが可能
②重複する不要なメッセージの削除が可能

以下に非同期POSTの例を示す。

// NSNotificationを使った簡単なイベント伝達のサンプル
static NSString* MySenderNotifyName = @"MySenderValueChanged";

// _xの値が10の倍数になったときに現在の値を通知する
@interface Sender : NSObject
{
    int _x;
}

@property(assign, nonatomic, readonly) int x;
-(int)increment;

@end

@implementation Sender

@synthesize x = _x;

-(id)init {
    self = [super init];
    
    if (self) {
        _x = 0;
    }
    
    return self;
}

-(int)increment {
    if (0 == ++ _x % 10) {
        NSNumber* val = [NSNumber numberWithInt:_x];
        NSNotification* notification = [NSNotification notificationWithName:MySenderNotifyName object:val];
        NSNotificationQueue* queue = [[NSNotificationQueue defaultQueue];
        [queue enqueueNotification:notification postingStyle:NSPostNow/* NSPostWhenIdle */]];
        
        // 非同期ポストが必要ない場合は以下の呼び出しでOK
        //[[NSNotificationCenter defaultCenter] postNotificationName:MySenderNotifyName object:self];
    }
    
    return _x;
}

@end

// Senderからの通知を受け取ったらコンソール出力を行う
@interface Receiver : NSObject
-(void)output:(NSNotification*)notification;
@end

@implementation Receiver

-(void)output:(NSNotification*)notification {
    NSNumber* val = (NSNumber*)notification.object;
    NSLog(@"%d", val.intValue);
}

@end


// テスト用メイン関数
int main(int argc, const char** argv)
{
    @autoreleasepool {
        // ReceiverをObserverとして通知センターに登録する
        Receiver* receiver = [[[Receiver alloc] init] autorelease];
        [[NSNotificationCenter defaultCenter] addObserver:receiver
                                                 selector:@selector(output:)
                                                     name:MySenderNotifyName
                                                   object:nil];
        
        // Senderに対して何か処理を行う
        // 条件が満たされれば、SenderからReceiverに自動的に通知が行われる
        Sender* sender = [[[Sender alloc] init] autorelease];
        
        for (int i = 0; 1000 > i; ++ i) {
            [sender increment];
        }
    }
}

上記のコード例は意味のないマルチスレッドであるが、デバッガで追ってみれば分かる通り、完全に非同期処理が行われている。自前でスレッドを用意したわけでもないのに、イベントの送信元はメッセージをPOSTするとすぐに復帰するのだ。

ここでは詳細は割愛するが、UIにおける画面の再描画メッセージのような、大量に通知されても意味のないようなメッセージを、受信先にPOSTする前に削除、あるいはマージしてしまう仕組みも備わっている。

このように、NSNotificationCenterは今まで紹介した3つのイベント伝達の仕組みの中でも最も高度な機能を持っており、特にバックグラウンドで稼働するスレッドから、別のスレッド(主にメインスレッド)へのメッセージ通知において大きな役割を果たす。実際にiOS内部でどのように使われているのかというと、サウンド再生のイベント通知や、デバイスの向きの変更通知など、ハードウェア寄りの非同期イベント通知で使用されている。

通知センターにはこういった高度な面があるからして、やはり、最初に紹介した順、

プロトコルデリゲート
・KVO
・NSNotificationCenter

の順に適用を検討するのが良い。