愛と勇気と缶ビール

ふしぎとぼくらはなにをしたらよいか

EKEventStore#requestAccessToEntityTypeのcallbackがmain thread以外の場所で実行される件とその対処

前略。

iOS6から、iOSでもAndroidのように端末内のリソースにアクセスする際にはユーザの許可が必要になりました。といってもAndroidのようにインストール時にpermissionを求めるわけではなく、機能毎にアクセス許可を求めるメソッドが実装されている感じです。こういう仕組みをOSのバージョンアップで急に持ち込んでくるApple先生マジ鬼畜。少なくとも開発者にとっては。

で、EventKit(端末内のカレンダーとかにアクセスするFramework)のEKEventStoreにもそういうメソッドがあり、これを呼ぶと「許可しますか?」みたいなダイアログが出てきます。このメソッド自体は、それに対してユーザが何らかのアクションを返すと呼ばれるコールバック関数をblockで指定するようなインタフェースになっております。下のページのrequestAccessToEntityType:completion:がそれです。

http://developer.apple.com/library/ios/#documentation/EventKit/Reference/EKEventStoreClassRef/Reference/Reference.html

このインタフェースが非同期になっているのがそもそもちょっとアレなのですが、例えばユーザに許可を貰えなかったとして、そのアプリではカレンダーへのアクセス許可が必須なんだよ!フォルア!という場合はUIAlertViewでも出してユーザに要求したい所です。これを素直に書くと

[self.eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
    if ( granted == NO ) {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"お知らせ" message:@"カレンダー操作の許可をくださいちょんまげ" delegate:self cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
        [alert show];
    }
    else {
        self.calendar = [self getCalendar];
    }
}];

↑このようになるのですが、これは死にます。理由としては、requestAccessToEntityTypeに渡したblockが実行されるthreadがmain threadではないからですね。iOSではmain thread以外のthreadからUIを触ると死ぬので、そのせい。(まぁそもそもこういうことをcallbackの中でやるのはよろしくない可能性はある)

これを避けるには、ここにあるようにGCDを用いてUIAlertViewの表示を明示的にmain threadで行います。

[self.eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
    if ( granted == NO ) {
        dispatch_async(dispatch_get_main_queue(), ^{
            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"お知らせ" message:@"カレンダー操作の許可をくださいちょんまげ" delegate:self cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
            [alert show];
        });
    }
    else {
        self.calendar = [self getCalendar];
    }
}];

ちなむと、現在いるthread(というか、GCDのqueue)を確認するには単に以下のようなコードを挟めばよいだけです。

dispatch_queue_t queue = dispatch_get_current_queue();
NSLog(@"%@", queue);

これを上記requestAccessToEntityTypeのcallback-block内で呼ぶと

2012-10-29 01:30:12.547 xxx[1419:110b] <OS_dispatch_queue_root: com.apple.root.default-priority[0x3b939280]>

と、確かにmain queueでないことが分かります。ちなみにmain queueで実行されているようなblockというかコードの場合、

2012-10-29 01:30:12.856 xxx[1419:907] <OS_dispatch_queue: com.apple.main-thread[0x3b93a580]>

このような表記となります。


EKEventStore#requestAccessToEntityTypeしか確認していませんが、他のpermission求める系メソッドのcallbackも同様なのではないか、と予想。


詳解 Objective-C 2.0 第3版

詳解 Objective-C 2.0 第3版