3

In my app, I have to retrieve certain properties of the users contacts. For example, I need to retrieve a contact's first name, last name, middle name, nickname, organization, job title, department, birthday, email, etc. I have some methods to retrieve these properties, and only a couple work, even though they all are very similar. Here is my code for one method that is working (first name) and one that is not (job title):

+(NSString *)fetchFirstnameForPersonID: (NSUInteger)identifier{

    NSString *firstName;
    ABRecordRef currentPerson = (__bridge ABRecordRef)[[PSAddressBook arrayOfContacts] objectAtIndex:identifier];

    //If the first name property exists
    if ((__bridge_transfer NSString *)ABRecordCopyValue(currentPerson, kABPersonFirstNameProperty) != NULL){
    
        firstName = (__bridge_transfer NSString *)ABRecordCopyValue(currentPerson, kABPersonFirstNameProperty);        
    }    
    //If the first name property does not exist
    else{
        firstName = @"NULL";
    }
    return firstName;    
}

+(NSString *)fetchJobTitleForPersonID: (NSUInteger)identifier{

    NSString *jobTitle;
    ABRecordRef currentPerson = (__bridge ABRecordRef)[[PSAddressBook arrayOfContacts] objectAtIndex:identifier];

    if ((__bridge_transfer NSString *)ABRecordCopyValue(currentPerson, kABPersonJobTitleProperty) != NULL){
    
        jobTitle = (__bridge_transfer NSString *)ABRecordCopyValue(currentPerson, kABPersonJobTitleProperty);        
    }    
    else{
    
        jobTitle = @"NULL";
    }
    return jobTitle;    
}

arrayOfContacts is a class method defined like this:

+(NSArray *)arrayOfContacts{

    //Creates an ABAddressBookRef instance containing the data from the address book database
    ABAddressBookRef addressBook = ABAddressBookCreate();

    //Creates an NSArray from the CFArrayRef using toll-free bridging
    NSArray *arrayOfPeople = (__bridge_transfer NSArray *)ABAddressBookCopyArrayOfAllPeople(addressBook);

    CFRelease(addressBook);

    return arrayOfPeople;
}

These methods are defined in a model class called "PSPropertyFetcher". In my root view controller, I put some NSLog statements in viewDidLoad to see if the property fetcher methods are working properly. Here is my code for the tests:

NSLog(@"Property Fetchers Test\n");

for(NSUInteger i = 0; i <= ([PSAddressBook contactsCount]-1); i++){

    NSLog(@"First Name: %@", [PSPropertyFetcher fetchFirstnameForPersonID:i]);
    NSLog(@"Middle Name: %@", [PSPropertyFetcher fetchMiddlenameForPersonID:i]);
    NSLog(@"Last Name: %@", [PSPropertyFetcher fetchLastnameForPersonID:i]);
    NSLog(@"Organization: %@", [PSPropertyFetcher fetchOrganizationForPersonID:i]);
    NSLog(@"Department: %@", [PSPropertyFetcher fetchDepartmentForPersonID:i]);        
    NSLog(@"Job Title: %@\n\n", [PSPropertyFetcher fetchJobTitleForPersonID:i]);        
}

This works only partly; this is the output:

2012-06-27 10:37:30.094 Guess Who![80103:f803] Property Fetchers Test

2012-06-27 10:37:30.108 Guess Who![80103:f803] First Name: Jod

2012-06-27 10:37:30.114 Guess Who![80103:f803] Middle Name: Bob

2012-06-27 10:37:30.118 Guess Who![80103:f803] Last Name: Satson

2012-06-27 10:37:30.122 Guess Who![80103:f803] Organization: Johnson and Johnson

2012-06-27 10:37:30.125 Guess Who![80103:f803] Department: NULL

2012-06-27 10:37:30.128 Guess Who![80103:f803] Job Title: NULL


2012-06-27 10:37:30.136 Guess Who![80103:f803] First Name: Shemairan

2012-06-27 10:37:30.166 Guess Who![80103:f803] Middle Name: Daitran

2012-06-27 10:37:30.179 Guess Who![80103:f803] Last Name: Catairan

2012-06-27 10:37:30.184 Guess Who![80103:f803] Organization: Shmairo and Co.

2012-06-27 10:37:30.188 Guess Who![80103:f803] Department: NULL

2012-06-27 10:37:30.193 Guess Who![80103:f803] Job Title: NULL


2012-06-27 10:37:30.202 Guess Who![80103:f803] First Name: Alex

2012-06-27 10:37:30.207 Guess Who![80103:f803] Middle Name: John

2012-06-27 10:37:30.213 Guess Who![80103:f803] Last Name: Corn

2012-06-27 10:37:30.219 Guess Who![80103:f803] Organization: Apple

2012-06-27 10:37:30.225 Guess Who![80103:f803] Department: NULL

2012-06-27 10:37:30.230 Guess Who![80103:f803] Job Title: NULL

In the iOS Simulator Contacts application, I made sure to fill out every field for each contact, but for some reason, the "Department" and "Job Title" fields are not printing correctly.

Basically, I'm wondering what's wrong with my "Job Title" and "Department" fetcher methods.

Thanks in advance!

Community
  • 1
  • 1
pasawaya
  • 11,515
  • 7
  • 53
  • 92

4 Answers4

6

While it doesn't seem likely on such a small sampling of an address book, you do have a memory leak here that could be blowing things out.

Assuming you are using arrayOfContacts to determine [self contactsCount], you will be leaking 2 AddressBook items every time through every loop in those class methods.

In this small sample, you will have only leaked 48 AddressBooks by the time you start contactsWithJobTitleProperty. But if you've pulled this from a larger example where you're repeatedly attempting to determine these values on a much larger AddressBook, you could have hundreds of dangling AddressBook objects hanging around.

Add CFRelease as in the following snippet, and see if it helps.

+(NSArray *)arrayOfContacts{

    //Creates an ABAddressBookRef instance containing the data from the address book database
    ABAddressBookRef addressBook = ABAddressBookCreate();

    //Creates an NSArray from the CFArrayRef using toll-free bridging
    NSArray *arrayOfPeople = (__bridge_transfer NSArray *)ABAddressBookCopyArrayOfAllPeople(addressBook);
    CFRelease(addressBook);

    return arrayOfPeople;
}

(fwiw, a separate thing that will crash your program is that you are using NSUInteger as your loop counter type, but you test against a value that could potentially be -1 ... and since 0 is the floor for NSUInteger, if your address book has no contacts in it, this will fail. but that is not why this crash is occurring.)

john.k.doe
  • 7,533
  • 2
  • 37
  • 64
  • I'll try that out and get back to you. – pasawaya Jun 26 '12 at 23:02
  • Now it doesn't crash and the organization method returns the correct value, but some of the other methods for other properties do not return the correct values. – pasawaya Jun 27 '12 at 00:03
  • what "organization" method? (i suppose i could assume you mean you have another class method like those above that retrieves the number of contacts with property kABPersonOrganizationProperty.) what other methods do not return correct values? not all types return the same type when you retrieve them, so you must make sure your __bridge or _bridge_transfer makes sense in every case. – john.k.doe Jun 27 '12 at 00:12
  • Sorry. The job title method returns the number of contacts with the job title property filled out. The organization method is the same but checks the organization property. I have a `__bridge_transfer` for every method like this (and I have a method for each property, 31 in total). In what situation would a `__bridge_transfer` not make sense? – pasawaya Jun 27 '12 at 00:25
  • sorry, i was not clear. i meant the type used within the __bridge or __bridge_transfer . i think you are using the __bridge/__bridge_transfer correctly in every one of these cases, but the type coming back from the address book may not always be a type that transfers to NSString, and if you do the __bridge/__bridge_transfer from something that's not a string to NSString, i would think you could incur trouble. – john.k.doe Jun 27 '12 at 00:28
  • Ok. I'll check my method. Thanks. – pasawaya Jun 27 '12 at 00:32
2

It looks to me like you may be accessing a NULL value, have you tried testing if it's NULL before performing the bridge transfer?

  • I don't test if it's a NULL value (which I should so thanks) but I made sure to add a value for each contact for every field in the simulator. – pasawaya Jun 26 '12 at 10:59
1

This might not solve your problem, but I would a couple of adjustments if I were you:

+(NSUInteger)contactsWithJobTitleProperty{
    NSUInteger contactNumber = 0;
    // don't call arrayOfContacts in the loop -- 
    // when you do this you are creating a new address book 
    // each time the loop executes
    NSArray *contacts = [self arrayOfContacts];
    for(NSUInteger increment = 0; increment <= (contacts.count-1); increment++){
        ABRecordRef currentPerson = (__bridge ABRecordRef)[contacts objectAtIndex:increment];
        NSString *valueTest = (__bridge_transfer NSString *)ABRecordCopyValue(currentPerson, kABPersonJobTitleProperty);

        if(valueTest != NULL) {
            contactNumber++;
        }
    }
    return contactNumber;
}

Also, @john.k.doe mentioned, make sure to use CFRelease(addressBook); in your arrayOfContacts method.

Michael Frederick
  • 16,664
  • 3
  • 43
  • 58
  • Thanks! I changed my code so that now, I create an address book like this `NSArray *contacts = [PSAddressBook arrayOfContacts]` before the for loop. I also added the `CFRelease(addressBook)` line to `arrayOfContacts`. – pasawaya Jun 27 '12 at 14:22
0

Just encountered the same issue. See Jokinryou Tsui's answer here.

Basically, if there is a situation where you have different sources of contacts available, and they are not all local (the default), you could end up with a mismatch of counts of how many contacts you're pulling, and try to access an ABRecordRef at an index that doesn't exist. The ABRecordRef will have a value (so a NULL check alone won't do it), but that value will be garbage and you'll get the BAD ACCESS when trying to copy any of its values.

Hope this helps.

Community
  • 1
  • 1
Chase Holland
  • 2,178
  • 19
  • 23