iPhone OS4 における非同期処理の挙動変更にまつわる試行錯誤

とあるアプリがiOS4でちゃんと動いてないとのことで、
ソースを追いかけつつ挙動を確認してみたところ
どうやらfor文の中で途中でNotification発行してるのに、

- (void)createItems {

    for (int i = 0; i < 200; i++) {
        // 処理1
        ...
        [self.items addObject:item];

        // 2個以上処理したところでお知らせ
        if ([self.items count] > 1) {
            NSNotification *notify =
                [NSNotification notificationWithName:NOTIFICATION_ITEM_CREATED
                                              object:self
                                            userInfo:nil];
            [[NSNotificationCenter defaultCenter] postNotification:notify];
        }
        
        // 描画処理をmainThreadで行う
        [self.tableView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO];
    }	 
}


このNotification通知の監視側(Observer)での通知受領後の処理が、
for文終わるまで待っちゃってることが判明。
(ほんとは通知発行した直後に非同期処理してほしい)


で、このcreateItems()を

NSInvocationOperation *operation =
    [[[NSInvocationOperation alloc] initWithTarget:self
                                          selector:@selector(createItems)
                                            object:nil] autorelease];
NSOperationQueue *q = [[[NSOperationQueue alloc] init] autorelease];
[q addOperation:operation];


こんな感じでNSOperationで実行しているのだけど、
どうやらこのへんの挙動にiOS4で変更があったらしい。
参考ページ



試しに前記ページに書かれているように、

// NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];
NSOperationQueue *queue = [NSOperationQueue mainQueue];


としてメインスレッドで処理するよう指定したら、
tableViewのreloadDataが非同期処理されなくなってしまった。
reloadDataもメインスレッドで処理するよう指定してるから、そりゃそうか。


その代わりに通知受領後の処理はやってくれるようになったかといえば、
そんなこともなく、やっぱりfor文終わるまで処理されない。
(そもそもmainQueueはiOS4以降でしか動かせないとのことでこの方法は選択できない)



こんなのも試してみたけど、ダメ。

// NSInvocationOperation *operation =
// [[[NSInvocationOperation alloc] initWithTarget:self
//                                       selector:@selector(createItems)
//                                         object:nil] autorelease];
// NSOperationQueue *q = [[[NSOperationQueue alloc] init] autorelease];
// [q addOperation:operation];
[self performSelectorInBackground:@selector(createItems) withObject:nil];


これは最初の状態と同じで、for文終わるまで通知受領後の処理が行われない。
(結局同じことを別の書き方してるだけなのかも)


最終的に、監視側での通知受領後の処理をメインスレッドで行うよう指定することで、
所望の挙動になった。

// [loadingView removeFromSuperview];
[loadingView performSelectorOnMainThread:@selector(removeFromSuperview) withObject:nil waitUntilDone:NO];


これからわかる不具合時の挙動は

  • Notification発行処理自体は非同期で行われていた
  • Notification発行側の処理と監視側の処理が同じスレッドで行われていた


ってことだけどその真偽、根本的原因究明はドキュメントをいずれちゃんと読むということで。