web-dev-qa-db-ja.com

iOS 6でABAddressBookCreateWithOptionsメソッドを正しく使用するにはどうすればよいですか?

IOS 6のABAdressBookCreateWithOptionsおよびABAddressBookRequestAccessWithCompletionメソッドを理解しようとしています。

私が見つけることができたほとんどの情報は、「連絡先データへのアクセスを要求するには、ABAddressBookRequestAccessWithCompletion関数を呼び出した後、ABAddressBookCreateWithOptions関数を呼び出します」です。

これらの方法を組み合わせて、アプリケーションに連絡先へのアクセスを許可するかどうかを判断するようユーザーに警告する必要があると思いますが、それらを使用するとプロンプトが表示されません。

誰かが実際の例でこれらのメソッドを一緒に呼び出す方法のサンプルコードを提供できますか? (CFDictionary)オプションを作成するにはどうすればよいですか?非推奨のABAddressBookCreateメソッドを使用した作業コードがありますが、プライバシーの問題に対応するためにiOS 6に更新する必要があります。

ここに光を当てられる人に感謝します!

34
codeqi

NDAが解除されたので、配列を返すメソッドを置き換える必要がある場合のこれに対する私の解決策を示します。既存のコードの一部を潜在的に書き換える準備ができています。以下のDavidのソリューションをご覧ください。

ABAddressBookRef addressBook = ABAddressBookCreate();

__block BOOL accessGranted = NO;

if (ABAddressBookRequestAccessWithCompletion != NULL) { // we're on iOS 6
    dispatch_semaphore_t sema = dispatch_semaphore_create(0);

    ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
        accessGranted = granted;
        dispatch_semaphore_signal(sema);
    });

    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    dispatch_release(sema);    
}
else { // we're on iOS 5 or older
    accessGranted = YES;
}


if (accessGranted) {

    NSArray *thePeople = (__bridge_transfer NSArray*)ABAddressBookCopyArrayOfAllPeople(addressBook);
    // Do whatever you need with thePeople...

}

これが誰かを助けることを願っています...

83
Engin Kurutepe

この質問に対する私が見たほとんどの答えは、GCDで狂気の複雑なことを行い、最終的にメインスレッドをブロックするになります。それは必要はありません!

これが私が使用しているソリューションです(iOS 5およびiOS 6で動作します)。

- (void)fetchContacts:(void (^)(NSArray *contacts))success failure:(void (^)(NSError *error))failure {
  if (ABAddressBookRequestAccessWithCompletion) {
    // on iOS 6

    CFErrorRef err;
    ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, &err);

    if (err) {
      // handle error
      CFRelease(err);
      return;
    }

    ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
      // ABAddressBook doesn't gaurantee execution of this block on main thread, but we want our callbacks to be
      dispatch_async(dispatch_get_main_queue(), ^{
        if (!granted) {
          failure((__bridge NSError *)error);
        } else {
          readAddressBookContacts(addressBook, success);
        }
        CFRelease(addressBook);
      });
    });
  } else {
    // on iOS < 6

    ABAddressBookRef addressBook = ABAddressBookCreate();
    readAddressBookContacts(addressBook, success);
    CFRelease(addressBook);
  }
}

static void readAddressBookContacts(ABAddressBookRef addressBook, void (^completion)(NSArray *contacts)) {
  // do stuff with addressBook
  NSArray *contacts = @[];

  completion(contacts);
}
23
Nik

他の上位の回答には問題があります:

  • 6より古いiOSには存在しないAPIを無条件に呼び出すため、プログラムは古いデバイスでクラッシュします。
  • メインスレッドをブロックするため、システムが警告を発している間、アプリは応答せず、進行しません。

これが私のMRCの見解です。

        ABAddressBookRef ab = NULL;
        // ABAddressBookCreateWithOptions is iOS 6 and up.
        if (&ABAddressBookCreateWithOptions) {
          NSError *error = nil;
          ab = ABAddressBookCreateWithOptions(NULL, (CFErrorRef *)&error);
    #if DEBUG
          if (error) { NSLog(@"%@", error); }
    #endif
          if (error) { CFRelease((CFErrorRef *) error); error = nil; }
        }
        if (ab == NULL) {
          ab = ABAddressBookCreate();
        }
        if (ab) {
          // ABAddressBookRequestAccessWithCompletion is iOS 6 and up.
          if (&ABAddressBookRequestAccessWithCompletion) {
            ABAddressBookRequestAccessWithCompletion(ab,
               ^(bool granted, CFErrorRef error) {
                 if (granted) {
                   // constructInThread: will CFRelease ab.
                   [NSThread detachNewThreadSelector:@selector(constructInThread:)
                                            toTarget:self
                                          withObject:ab];
                 } else {
                   CFRelease(ab);
                   // Ignore the error
                 }
                 // CFErrorRef should be owned by caller, so don't Release it.
               });
          } else {
            // constructInThread: will CFRelease ab.
            [NSThread detachNewThreadSelector:@selector(constructInThread:)
                                     toTarget:self
                                   withObject:ab];
          }
        }
      }
22

これは元の質問と周辺的に関連していますが、他のどこかで言及されているのを見たことがなく、それを理解するのに約2日かかりました。アドレス帳の変更のためにコールバックを登録する場合、それはメインスレッド上になければなりません。

たとえば、このコードでは、sync_address_book_two()のみが呼び出されます。

ABAddressBookRequestAccessWithCompletion(_addressBook, ^(bool granted, CFErrorRef error) {
    if (granted) {
        ABAddressBookRegisterExternalChangeCallback (_addressBook, sync_address_book_one, NULL);
        dispatch_async(dispatch_get_main_queue(), ^{
            ABAddressBookRegisterExternalChangeCallback (_addressBook, sync_address_book_two, NULL);
        });
    }
});
2
Eli Burke