In one of my apps I am using dispatch_queue and inside this I declared a dispatch_asyc queue for checking the address book. Now when compiler comes to the return statement, it causes app to crash. Below is my source code.
dispatch_queue_t queue = dispatch_queue_create("abc", NULL);
dispatch_async(queue, ^{
// Request authorization to Address Book
ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, NULL);
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined) {
ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error) {
// First time access has been granted...
// All good.
completionBlock?completionBlock(YES):nil;
dispatch_async(queue, ^{
if (addressBookRef) {
CFRelease(addressBookRef);
};
});
return;
});
According to the documentation on Address Book, you cannot use ABAddressBookRef across threads
Important: Instances of ABAddressBookRef cannot be used by
multiple threads. Each thread must make its own instance by calling
ABAddressBookCreate.
See this question for some more ideas on how to do this:
Related
I am trying to get a JSON (NSString *) of the address book , in async task , however I want it to be asynchronously and with a completion block .
I have managed to retrieve that easy without the completion block , however when I am adding the completion block I have the following compiler error :
Incompatible block pointer types passing NSString *(^)(bool, CFErrorRef)' to parameter of type 'ABAddressBookRequestAccessCompletionHandler' (aka 'void (^)(bool, CFErrorRef)')
here is my code
+ (NSString *) getAddressBook:(id (^)())block {
ABAuthorizationStatus status = ABAddressBookGetAuthorizationStatus();
if (status == kABAuthorizationStatusDenied)
{
// if you got here, user had previously denied/revoked permission for your
// app to access the contacts, and all you can do is handle this gracefully,
// perhaps telling the user that they have to go to settings to grant access
// to contacts
[[[UIAlertView alloc] initWithTitle:nil message:#"This app requires access to your contacts to function properly. Please visit to the \"Privacy\" section in the iPhone Settings app." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
return nil;
}
CFErrorRef error = NULL;
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, &error);
if (error)
{
NSLog(#"ABAddressBookCreateWithOptions error: %#", CFBridgingRelease(error));
if (addressBook) CFRelease(addressBook);
return nil;
}
if (status == kABAuthorizationStatusNotDetermined)
{
// present the user the UI that requests permission to contacts ...
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) -> THE ERROR OCCURS HERE !
{...
I think the problem is within my completion blocks , but I can't find some good example/tutorial for my specific issue I would be really glad if some 1 could help me please.
Your problem is that you are declaring getAddressBook like this(if we strip block)
-(id)block
And you are trying to pass it as a function of type:
-(void)block:(BOOL)aBool error:(CFErrorRef*)error
Blocks are quite hard to get used with syntax(which is improved in swift) however I'm always refering to this site when not sure:
block syntax reference
UPDATE:
You have to actually wait for your async method finish. You may also refer to official documentation
+ (void) getAddressBook:((^)(NSString* result))block {
ABAuthorizationStatus status = ABAddressBookGetAuthorizationStatus();
if (status == kABAuthorizationStatusDenied)
{
// if you got here, user had previously denied/revoked permission for your
// app to access the contacts, and all you can do is handle this gracefully,
// perhaps telling the user that they have to go to settings to grant access
// to contacts
[[[UIAlertView alloc] initWithTitle:nil message:#"This app requires access to your contacts to function properly. Please visit to the \"Privacy\" section in the iPhone Settings app." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
block(nil);
}
CFErrorRef error = NULL;
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, &error);
if (error)
{
NSLog(#"ABAddressBookCreateWithOptions error: %#", CFBridgingRelease(error));
if (addressBook) CFRelease(addressBook);
block(nil);
}
if (status == kABAuthorizationStatusNotDetermined)
{
// present the user the UI that requests permission to contacts ...
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) -> THE ERROR OCCURS HERE !
{
NSString* result = whatever
block(result);
});
}
}
And use it like this:
[YourClass getAddressBook:^(NSString* result)
{
//now you really have result
}];
I am trying to understand better the use of dispatch_semaphore_wait. I get the idea of this use but i am not getting on Which thread i am actually wait. if i will do that on the Main thread i will block the main thread, or if i will do it like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error) {
dispatch_semaphore_signal(sema);
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
});
On which thread i will wait ?
or if i would use DISPATCH_QUEUE_PRIORITY_DEFAULT instead or DISPATCH_QUEUE_PRIORITY_BACKGROUND ?
In 1 and 2 you'd wait on the thread where the DISPATCH_QUEUE_PRIORITY_HIGH and DISPATCH_QUEUE_PRIORITY_BACKGROUND resides. In this case its the same "queue"/thread and the difference relies in the way the scheduler handles the selection of the tasks submitted (high, mid or low priority. Where low is equal to DISPATCH_QUEUE_PRIORITY_BACKGROUND and high as DISPATCH_QUEUE_PRIORITY_HIGH). The do not rely on the UI-thread what so ever. The dispatch_get_global_queue call does point to a thread that iOS creates for you. There are lots of guides on the net describing this and how the scheduling of tasks are handled in a multithreaded environment.
Google is your friend ;)
If you are attempting to find out synchronously if the user has given address book access I would suggest the following:
__block BOOL accessGranted = NO;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
accessGranted = granted;
if(semaphore) {
dispatch_semaphore_signal(semaphore);
}
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_release(semaphore);
semaphore = NULL;
return accessGranted;
You can't be sure in which thread will you wait by calling dispatch_semaphore_wait() in the global queue, because:
global queue is not a single thread, it is a pool of threads.
only serial queue can be one-threaded, but that is not specified in Apple Documentation.
So, to be sure you are not blocking anything, better to create your own serial queue for waiting the semaphore.
I want to retrieve contacts from AddressBook into my App. The code is having problem for first install, next time onwards it is working fine. For the first installation of App, if AddressBook code executes, the App hangs. After execution of dispatch_semaphore_wait line, the App hangs. I restarted the device, this time App works fine. It is happening if it is first install on new device. How can i fix this?
CFErrorRef * error = NULL;
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, NULL);
__block BOOL isaccess = NO;
if(ABAddressBookRequestAccessWithCompletion != NULL) {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
//ask to grand or deny access
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
isaccess = granted;
dispatch_semaphore_signal(sema);
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_release(sema);
}
this code has been posted before, and been used as well, from what i could gather. i'm in a situation where i need the code to NOT continue until i know if i have access to the contacts.
on Xcode 5.0.2 and iOS 6, this works just fine. on iOS 7, it hangs forever, and then when i kill the app the dialog box comes up asking to allow access to the contacts.
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);
}
else { // we're on iOS 5 or older
accessGranted = YES;
}
in trying to play with this, i then simply set a BOOL to be NO, and then set it to YES within the block. after the block, i put a while loop that checked for the variable being YES, then slept for 1 second. works perfectly fine on 6, on 7 i never reach the NSLog statement in the block, and i'm stuck forever in the while loop printing the log statement.
am i doing something really lame here? or is this method gone haywire on 7?
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, &error);
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
if (granted)
{
self.myAddressBook = addressBook;
}
done = YES;
NSLog(#"in block, done is %#", done ? #"YES" : #"NO");
didGrant = granted;
//dispatch_semaphore_signal(sema);
});
while (!done)
{
NSLog(#"done is %#", done ? #"YES" : #"NO");
sleep(1);
}
I had the same problem, and I realised that the Dialog box that requests access to the contacts blocks the app anyways, so maybe there's a deadlock. So I just ditched the semaphores and did something like this (tested and works on iOS 7.1.1):
ABAddressBookRef addressBook = ABAddressBookCreate();
MyController * __weak weakSelf = self;
if (ABAddressBookRequestAccessWithCompletion != NULL)
{ // we're on iOS 6
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined)
{
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf accessGrantedForAddressBook];
});
});
}
else
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized); //Maybe add something here to handle kABAuthorizationStatusRestricted
[self accessGrantedForAddressBook];
}
}
else // we're on iOS 5 or older
[self accessGrantedForAddressBook];
which is quite similar to what Apple does in their documentation (search for ABAddressBookRequestAccessWithCompletion). Besides, what's the point of ABAddressBookRequestAccessWithCompletion being asynchronous and waiting for it (see here)..
On a Stackoverflow question (see "Source 1" at the bottom), I see the following pattern when working with AddressBook in which the programmer wants to block the main thread until the user has granted (or denied) access to his/her AddressBook:
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...
}
but according to the documentation (ABAddressBook Reference), the completion handler is executed on an arbitrary queue, which means it could be executed on the main queue. If the completion handler were placed on the main queue, then the above code would result in a deadlock. Is there any documentation guaranteeing that the completion handler will not be placed on the main queue?
ABAddressBook Reference: "The completion handler is called on an arbitrary queue. If your app uses an address book throughout the app, you are responsible for ensuring that all usage of that address book is dispatched to a single queue to ensure correct thread-safe operation."
Source:
First answer to: How do I correctly use ABAddressBookCreateWithOptions method in iOS 6?