Reading email address from contacts fails with weird memory issue - iphone

I'm stumped.
I'm trying to get a list of all the email address a person has.
I'm using the ABPeoplePickerNavigationController to select the person, which all seems fine. I'm setting my
ABRecordRef personDealingWith;
from the person argument to
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier {
and everything seems fine up till this point.
The first time the following code executes, all is well.
When subsequently run, I can get issues. First, the code:
// following line seems to make the difference (issue 1)
// NSLog(#"%d", ABMultiValueGetCount(ABRecordCopyValue(personDealingWith, kABPersonEmailProperty)));
// construct array of emails
ABMultiValueRef multi = ABRecordCopyValue(personDealingWith, kABPersonEmailProperty);
CFIndex emailCount = ABMultiValueGetCount(multi);
if (emailCount > 0) {
// collect all emails in array
for (CFIndex i = 0; i < emailCount; i++) {
CFStringRef emailRef = ABMultiValueCopyValueAtIndex(multi, i);
[emailArray addObject:(NSString *)emailRef];
CFRelease(emailRef);
}
}
// following line also matters (issue 2)
CFRelease(multi);
If compiled as written, the are no errors or static analysis problems. This crashes with a
*** -[Not A Type retain]: message sent to deallocated instance 0x4e9dc60
error.
But wait, there's more! I can fix it in either of two ways.
Firstly, I can uncomment the NSLog at the top of the function. I get a leak from the NSLog's ABRecordCopyValue every time through, but the code seems to run fine.
Also, I can comment out the
CFRelease(multi);
at the end, which does exactly the same thing. Static compilation errors, but running code.
So without a leak, this function crashes. To prevent a crash, I need to haemorrhage memory. Neither is a great solution.
Can anyone point out what's going on?

It turned out that I wasn't storing the ABRecordRef personDealingWith var correctly. I'm still not sure how to do that properly, but instead of having the functionality in another routine (performed later), I'm now doing the grunt-work in the delegate method, and using the derived results at my leisure. The new (working) routine:
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person {
// as soon as they select someone, return
personDealingWithFullName = (NSString *)ABRecordCopyCompositeName(person);
personDealingWithFirstName = (NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty);
// construct array of emails
[personDealingWithEmails removeAllObjects];
ABMutableMultiValueRef multi = ABRecordCopyValue(person, kABPersonEmailProperty);
if (ABMultiValueGetCount(multi) > 0) {
// collect all emails in array
for (CFIndex i = 0; i < ABMultiValueGetCount(multi); i++) {
CFStringRef emailRef = ABMultiValueCopyValueAtIndex(multi, i);
[personDealingWithEmails addObject:(NSString *)emailRef];
CFRelease(emailRef);
}
}
CFRelease(multi);
return NO;
}

I ran into a similar problem. The problem might lie at how you set your
ABRecordRef personDealingWith;
It seems that you can't just do:
ABRecordRef personDealingWith = person;
Because personDealingWith remains null. Instead what I did is:
ABRecordID personID = ABRecordGetRecordID(person);
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, NULL);
personDealingWith = ABAddressBookGetPersonWithRecordID(addressBook, personID);

Related

Getting twitter user from Address Book

I'm trying to get the Twitter username from a contact in the address book but I'm not able to do it
I'm using this code that I found "googling":
- (NSString*)getTwitterUsernameFromRecord:(ABRecordRef)record {
NSString * twitterUsername = nil;
ABMultiValueRef socials = ABRecordCopyValue(record, kABPersonSocialProfileProperty);
if (!socials) {
return nil;
}
CFIndex socialsCount = ABMultiValueGetCount(socials);
for (int k=0 ; k<socialsCount ; k++) {
CFDictionaryRef socialValue = ABMultiValueCopyValueAtIndex(socials, k);
if(CFStringCompare( CFDictionaryGetValue(socialValue, kABPersonSocialProfileServiceKey), kABPersonSocialProfileServiceTwitter, 0)==kCFCompareEqualTo) {
twitterUsername = (NSString*) CFDictionaryGetValue(socialValue, kABPersonSocialProfileUsernameKey);
}
CFRelease(socialValue);
if(twitterUsername)
break;
}
CFRelease(socials);
return twitterUsername;
}
I've put the if to validate the "socials" array is nil because I was getting exception when trying to get "socials" array count.
I've tried this code in the simulator and in a real device with contacts which twitter info is filled but the "socials" array I get is always nil. I'm getting the wrong property from the record? Any help?
Thank you in advance
your function works for me almost as-is, aside from needing to bridge cast the twitter username (which is just an ARC thing). how are you accessing the address book?
when i use this code to call your function:
ABAddressBookRef addressBook = ABAddressBookCreate();
CFArrayRef people = ABAddressBookCopyArrayOfAllPeople(addressBook);
CFIndex contactCount = ABAddressBookGetPersonCount(addressBook);
for( int i = 0; i<contactCount; i++ )
{
ABRecordRef ref = CFArrayGetValueAtIndex(people, i);
NSString *twitterHandle = [self getTwitterUsernameFromRecord:ref];
if (twitterHandle) {
NSLog(#"record %d has twitter name %#", i, twitterHandle);
}
}
i get this console output:
2012-06-26 12:09:40.864 AddressBookTest[2246:707] record 57 has twitter name alexshepard

Using an ABRecordRef #property triggers EXC_BAD_ACCESS

I'm trying to implement some code that writes to a contact using code from this Address Book guide. I've declared a property in my ViewController.h:
#property ABRecordRef mABRecordRef;
Then I retrieve a record:
- (BOOL)peoplePickerNavigationController:
(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person {
At this point, I am able to successfully call ABRecordSetValue:
CFErrorRef anError = NULL;
bool didSet;
didSet = ABRecordSetValue(person, kABPersonFirstNameProperty, (CFStringRef)#"foo", &anError);
if( didSet )
{
// ...
In this method, I also save the record:
self.mABRecordRef = person;
After some user action (in another method), I attempt to use this record in the same way:
CFErrorRef anError = NULL;
bool didSet;
didSet = ABRecordSetValue(self.ABRecordRef, kABPersonFirstNameProperty, (CFStringRef)#"foo", &anError);
But I get an error:
EXC_BAD_ACCESS(code=2, address=0x...
Why is this? I am assuming (perhaps incorrectly) that my ABRecordRef isn't released between my first and second uses.
I've also tried saving off that record's UniqueId as well as peoplePicker.addressBook, then later on pulling the same record back, but I get a similar error.

How to read the Address Book as fast as other apps (like Viber, Whatsapp...)

I am writing an app where I need to read the Address Book data to search for some contacts of interest, something similar to what many apps do nowadays (like Viber, Whatsapp, Tango...). I need to do matching so I send the data to a server and reply to the client which contacts have the same app installed on their devices.
I have no problem in the logic or mechanism of the idea, my problem is speed! I was able to do what I want but the process took 27 seconds to finish on iPhone4 with 500 contacts on it. On the same device if we try Viber or Whatsapp (or any similar app) the process takes less than 5 seconds.
My method is very straightforward, I do a for loop and read everything. How can I do the same thing but much faster like other apps?
Here is the code that I use:
//variable definitions
ABAddressBookRef addressBook = ABAddressBookCreate();
CFArrayRef people = ABAddressBookCopyArrayOfAllPeople(addressBook);
CFMutableArrayRef peopleMutable = CFArrayCreateMutableCopy(kCFAllocatorDefault, CFArrayGetCount(people), people);
//sort the contents of the Mutable Array
CFArraySortValues(peopleMutable, CFRangeMake(0, CFArrayGetCount(peopleMutable)), (CFComparatorFunction) ABPersonComparePeopleByName, (void*) ABPersonGetSortOrdering());
//read the Address Book
NSString *fullName, *number;
ABRecordRef record = ABPersonCreate();
ABMutableMultiValueRef multi;
int contactID;
int nameCount=0;//used to count the names in the string to send to server
NSMutableString *strNamesToSend = [[NSMutableString alloc] init];
for(CFIndex i=0; i< CFArrayGetCount(people); i++)
{
record = CFArrayGetValueAtIndex(people, i);
multi = ABRecordCopyValue(record, kABPersonPhoneProperty);
//Contact ID
contactID = (int)ABRecordGetRecordID(record);
//Full Name
fullName = [NSString stringWithFormat:#"%# %#", (NSString *)ABRecordCopyValue(record, kABPersonFirstNameProperty), (NSString *)ABRecordCopyValue(record, kABPersonLastNameProperty)];
fullName = [fullName stringByReplacingOccurrencesOfString:#" (null)" withString:#""];
//fill data into AddressBook Table
if(dbOpen == SQLITE_OK)
{
//pure sqlite3 work to save the names in my app
}
//Get multiple numbers from each user (if any)
for(CFIndex j=0; j<ABMultiValueGetCount(multi); j++)
{
number = (NSString *)ABMultiValueCopyValueAtIndex(multi, j);
nameCount++;
//fill data into AllNumbers Table
if(dbOpen == SQLITE_OK)
{
//another sqlite3 work to save the numbers
}
}
//send to the server every 29 numbers so we don't send all the 500 numbers at once
if(nameCount > 29)
{
//send to server
}
Have you tried profiling your code at all? A profiler should be able to quickly identify the slow parts of your code.
From a very brief inspection, I notice you're counting the array size at each iteration rather than just once. Move it out of your loop:
int count = CFArrayGetCount(people);
for (CFIndex i = 0; i < count; i++)
You don't detail the SQL calls you're making, but the fact you're checking for SQLITE_OK implies that you're opening the database each time through the loop. If this is the case you should move this call outside your loop rather than opening the database each time.
I notice that nameCount isn't being reset, which means that once it reaches 29, your if case will be hit every single time, causing a huge number of network requests.

iPhone address book problem

I'm having a bit of an issue with using the address book to get the names of the contacts from the device into my own contacts view within my application.
The code I have works fine on the emulator but I when tested on an iPhone 4 it will crash, the application seems to work fine if there are two or less contacts but 3 or more and the application crashes.
Here is the code I am using to get the names of contacts into an array.
ABAddressBookRef addressBook;
bool wantToSaveChanges = YES;
bool didSave;
CFErrorRef error = NULL;
addressBook = ABAddressBookCreate();
listOfContacts = [[NSMutableArray alloc]init];
int i;
int len = (int) ABAddressBookGetPersonCount(addressBook);
for(i = 1; i< (len+1); i++){
ABRecordRef person = ABAddressBookGetPersonWithRecordID(addressBook, (ABRecordID) i);
NSString* name = (NSString *)ABRecordCopyCompositeName(person);
ABMultiValueRef number = (NSString *)ABRecordCopyValue(person,kABPersonPhoneProperty);
NSString *mobileNum = (NSString *)ABMultiValueCopyValueAtIndex(number, 0 );
NSLog(#"Name = %#", name);
NSLog(#"Number = %#", mobileNum);
[listOfContacts addObject:name];
[name release];
[mobileNum release];
}
if(ABAddressBookHasUnsavedChanges(addressBook)){
if(wantToSaveChanges){
didSave = ABAddressBookSave(addressBook, &error);
if(!didSave){
//Error
}
}
else{
ABAddressBookRevert(addressBook);
}
}
When it crashes this is the line that gets highlighted in Xcode:
NSString* name = (NSString *)ABRecordCopyCompositeName(person);
And the error states:
Thread 1: Program received signal: "EXC_BAD_ACCESS"
Can anyone see what the problem might be? I dont understand why it would work on the emulator but not on the device? And also why it works for up to two contacts but not 3 or more??
Just a guess:
ABRecordRef person = ABAddressBookGetPersonWithRecordID(addressBook, (ABRecordID) i);
This line looks fishy for me. I doubt that the record IDs are numbered from 1 to whatever. Especially if you have deleted an entry.
This would explain why it works on the simulator, I guess you just added some test contacts and never deleted one.
Here is how I solved it:
(Note how I make sure to call only active records, also I created a customized Contact class)
This code takes care of edge case like: email/phone doesn't exist, or exists more than once...
+(NSArray *)getAddressBook{
ABAddressBookRef addressBook;
bool wantToSaveChanges = YES;
bool didSave;
CFErrorRef error = NULL;
addressBook = ABAddressBookCreate();
NSMutableArray *listOfContacts = [[NSMutableArray alloc]init];
CFArrayRef array=ABAddressBookCopyArrayOfAllPeople(addressBook);
int len=CFArrayGetCount(array);
for (int i = 0; i<len; i++){
ABRecordRef person = CFArrayGetValueAtIndex(array, i);
if (ABRecordGetRecordType(person)==kABPersonType){
NSString *firstName = (NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty);
NSString *lastName = (NSString *)ABRecordCopyValue(person, kABPersonLastNameProperty);
ABMultiValueRef emails = (ABMultiValueRef)ABRecordCopyValue(person,kABPersonEmailProperty);
ABMultiValueRef numbers = (ABMultiValueRef)ABRecordCopyValue(person,kABPersonPhoneProperty);
int sumEmails=ABMultiValueGetCount(emails);
int sumNumbers=ABMultiValueGetCount(numbers);
for (int j=0; j<(sumNumbers>sumEmails?sumNumbers:sumEmails); j++) {
ACL_AB_Contact *contact=[[ACL_AB_Contact alloc]initWithFirstName:firstName LastName:lastName];
if (j<sumEmails){
contact.emailAddress=(NSString *)ABMultiValueCopyValueAtIndex(emails,j);
}
if (j<sumNumbers){
contact.phoneNumber=(NSString *)ABMultiValueCopyValueAtIndex(numbers,j);
}
[contact logContact];
[listOfContacts addObject:contact];
[contact release];
}
}
}
if(ABAddressBookHasUnsavedChanges(addressBook)){
if(wantToSaveChanges){
didSave = ABAddressBookSave(addressBook, &error);
if(!didSave){
//Error
}
}
else{
ABAddressBookRevert(addressBook);
}
}
return [listOfContacts autorelease];
}
The record IDs are dynamic. It means that if you add 2 contacts and then remove the first, you will have only a contact with id "2". So I wouln't use a for statement to get through the contacts. Follow the Address Book Programming Guide

kABPersonAddressProperty causes crashes under ABAddressBookGetPersonWithRecordID

I am trying to be an ABRecordRef that represents the contact info of a person from addressbook. I built two functions that calls a function to fill in a personal data structure with the info in ABRecordRef.
Here comes the function declarations for the three functions:
+ (NSMutableArray*) getAllContactProfiles{
NSMutableArray *listOfProfile = [[NSMutableArray alloc] init];
//---get the contact information for the api
ABAddressBookRef addressBook = ABAddressBookCreate();
CFArrayRef people = ABAddressBookCopyArrayOfAllPeople(addressBook);
CFIndex numberOfPeopleInAddressBook = ABAddressBookGetPersonCount(addressBook);
//<- Here I loop through all the contacts and pass the ABRecordRef into the following function
//---release the variables---
CFRelease(addressBook);
CFRelease(people);
[listOfProfile autorelease];
return listOfProfile;
}
The Following Function
+ (MSProfileEntry*) getPersonProfileThroughABRecordRef:(ABRecordRef) person{
MSProfileEntry *mockProfile;
ABRecordID recID=ABRecordGetRecordID(person);
//get the user name
CFStringRef firstName;
firstName = ABRecordCopyValue(person, kABPersonFirstNameProperty);//it goes wrong here!
CFStringRef lastName;
lastName = ABRecordCopyValue(person, kABPersonLastNameProperty);
//bla bla bla.. the rest of the code
}
Everything goes very well. However, when I try to get the ABRecordRef through ABAddressBookGetPersonWithRecordID like it is in the next method:
The Next Method
+ (MSProfileEntry*) getPersonProfileThroughContactId:(NSInteger*)contactId{
ABAddressBookRef addressBook = ABAddressBookCreate();
ABRecordRef person =
ABAddressBookGetPersonWithRecordID(addressBook, (ABRecordID)contactId);
CFRelease(addressBook);
if (person == nil) {
return nil;
}
return [MSContactUtil getPersonProfileThroughABRecordRef:person];
}
The whole app crashes on line:ABRecordCopyValue(person, kABPersonFirstNameProperty);.
The problem now is that ABRecordCopyValue(person, kABPersonFirstNameProperty); works perfectly fine with ABAddressBookCopyArrayOfAllPeople but causes the app to crash with ABAddressBookGetPersonWithRecordID.
Does anyone have any clue how to solve this problem? I really don't want to loop through entire contact base just to look for a contact.
It turned out to be a memory issue. I forgot to retain the "addressBook". By the time I executed the following line:
firstName = ABRecordCopyValue(person, kABPersonFirstNameProperty);
The "addressBook" had been cleaned out already. Somehow we still need "addressBook" while querying for the detail information in "person".
So, remember to put in the following line, and you will be safe.
CFRetain(addressBook);
Two things:
You pass (NSInteger*)contactId to getPersonProfileThroughContactId and after that you call ABAddressBookGetPersonWithRecordID(addressBook, (ABRecordID)contactId);. Actually you pass an address of the integer that holds the contact id and not the id itself...
You check if (person == nil), BUT person may not be nil - you should compare with NULL. I believe that it IS NULL in your case (because of my previous point).
These 2 things together cause the crash.
Just pass an integer as is - not its address...
EDIT:
Like this:
+ (MSProfileEntry*)getPersonProfileThroughContactId:(NSInteger)contactId