34

我正在尝试了解 iOS 6 中的ABAdressBookCreateWithOptionsABAddressBookRequestAccessWithCompletion方法。

我能找到的最多信息如下,“要请求访问联系人数据,请在调用该ABAddressBookRequestAccessWithCompletion函数后调用该ABAddressBookCreateWithOptions函数。”

我相信这些方法一起应该提醒用户决定是否允许应用程序访问联系人,但是当我使用它们时,我没有看到任何提示。

有人可以提供一些示例代码,说明在现实世界的示例中如何将这些方法一起调用吗?如何创建 ( CFDictionary) 选项?我有使用已弃用ABAddressBookCreate方法的工作代码,但需要更新到 iOS 6 以适应隐私问题。

提前感谢任何可以在这里有所启发的人!

4

4 回答 4

83

现在 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...

}

希望这可以帮助某人...

于 2012-09-20T10:22:36.897 回答
23

我在这个问题上看到的大多数答案都用 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);
}
于 2013-05-02T06:24:55.223 回答
22

另一个排名靠前的答案有问题:

  • 它无条件地调用iOS 6 之前不存在的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];
          }
        }
      }
于 2012-12-14T21:33:23.527 回答
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);
        });
    }
});
于 2014-02-05T19:37:17.490 回答