I am reading records from the AddressBook using the Apple provided API.
I am still getting my head around memory management and so CFStrings are confusing me at the moment.
This is how I am getting the properties:
//Get Basic properties
NSString* firstName = (NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty);
NSString* lastName = (NSString *)ABRecordCopyValue(person, kABPersonLastNameProperty);
NSNumber* record = [NSNumber numberWithInt:ABRecordGetRecordID(person)];
//Build Full Name
NSString* fullName=[self fullNameWith:firstName and:lastName];
//Get Phone number and Label
ABMultiValueRef phone = ABRecordCopyValue(person, property);
//Turn identifier into index
CFIndex index = ABMultiValueGetIndexForIdentifier(phone, identifier);
//Get the value and the label using the index
NSString *value =(NSString *)ABMultiValueCopyValueAtIndex(phone, index);
CFStringRef label = ABMultiValueCopyLabelAtIndex(phone, index);
//Get the localized value of hte label
NSString * localizedLabel = (NSString *)ABAddressBookCopyLocalizedLabel(label);
After that I use the values, the only thing is that I dont know if I should release them or not.
I would appreciate an answer that also helped me understand memory management better or that points me to the right direction.
Thank you!
The rule of thumb for Core Foundation is that any functions that include Copy or Create in their name will return an object that you are responsible for releasing. Apple's Memory Management Guide for Core Foundation explains this in a bit more detail.
Related
Using the ABPersonCreateVCardRepresentationWithPeople method for export, and the ABPersonCreatePeopleInSourceWithVCardRepresentation for import, I have successfully transfered contact data between devices. However, the data in the contact's "notes" field isn't transfered.
Here's my export function:
+(NSData*)exportContactsToVcard:(NSArray*)contacts
{
NSMutableArray *people = [NSMutableArray arrayWithCapacity:contacts.count];
ABAddressBookRef ab = ABAddressBookCreate();
for (Contact *contact in contacts)
{
ABRecordRef person = ABAddressBookGetPersonWithRecordID(ab,contact.contactId);
[people addObject:(__bridge id)person];
}
NSData *vCard = (__bridge NSData*)ABPersonCreateVCardRepresentationWithPeople((__bridge CFArrayRef) people);
return vCard;
}
and part of my import function:
+(NSArray*)importContactsFromVcardData:(NSData*)vcardData
{
NSMutableArray *addedContactIds = [NSMutableArray array];
ABAddressBookRef addressBook = ABAddressBookCreate();
ABRecordRef defaultSource = ABAddressBookCopyDefaultSource(addressBook);
NSArray *createdPeople = (__bridge_transfer NSArray*)ABPersonCreatePeopleInSourceWithVCardRepresentation(defaultSource,(__bridge CFDataRef)vcardData);
CFErrorRef error = NULL;
for (id person in createdPeople)
{
error = NULL;
ABRecordRef personRecord = (__bridge ABRecordRef)person;
NSString *notes = (__bridge NSString *)ABRecordCopyValue(personRecord, kABPersonNoteProperty);
In the last line, notes is always nil, even if the contact had notes before it was exported. All the other standard contact fields seem to be in place.
For example, if I replace the last line with:
NSString *firstName = (__bridge NSString *)ABRecordCopyValue(personRecord, kABPersonFirstNameProperty);
the firstName string will hold the contact's first name.
Any idea how I can work around this, and get the contact notes?
Thanks.
For testing purposes, you can export a vCard from Address Book. Then drag it to TextEdit to look at the various fields.
At the bottom you'll find something like this:
NOTE:This is a note!
Also, see this link for info on the vCard format:
http://en.wikipedia.org/wiki/VCard
ABPersonCreateVCardRepresentationWithPeople will only transfer the important contact details of a vCard...thats my identifying in much tests with the vCard and the iOS import-export function. It has many mistakes, e.g. if you try to import it with social networks like facebook, it won't do it with (just look in my other thread THREADLINK )
But if you try to add some vCard Information over ABNewPersonViewController, it will work perfectly.
I've got a doubt about using "CFStringRef". I've seen several examples of managing iPhone contacts and in most of them they use:
...
ABMultiValueRef emails = ABRecordCopyValue(person, kABPersonEmailProperty);
CFStringRef emailRef = ABMultiValueCopyValueAtIndex(emails, 0);
...
NSString *email = (NSString *)emailRef;
I don't know why CFStringRef is used instead of using casting:
...
ABMultiValueRef emails = ABRecordCopyValue(person, kABPersonEmailProperty);
NSString *email = (NSString *)ABMultiValueCopyValueAtIndex(emails, 0);
...
Is it conventional to use CFStringRef? Is it correct to use direct casting?
CFString is “toll-free bridged” with its Cocoa Foundation counterpart,
NSString. This means that the Core Foundation type is interchangeable
in function or method calls with the bridged Foundation object.
Therefore, in a method where you see an NSString * parameter, you can
pass in a CFStringRef, and in a function where you see a CFStringRef
parameter, you can pass in an NSString instance. This also applies to
concrete subclasses of NSString.
Both are same.
There is no difference, the first example just uses an extra temporary variable. Performing a straight cast is perfectly OK, although you should pay attention to the memory management aspect. It sounds like ABMultiValueCopyValueAtIndex returns a retained object although the documentation isn't clear.
I've made an iPhone app using ARC that accesses every entry in the address book, and then every address for every person. The data is stored in CFArrays, which are toll-free bridged to NSArrays. The code is below.
ABAddressBookRef addressBook = ABAddressBookCreate();
CFArrayRef arrayRef = ABAddressBookCopyArrayOfAllPeople(addressBook);
NSArray *peopleArray =[(__bridge NSArray *) arrayRef copy];
CFRelease(arrayRef);
arrayRef = nil;
for(id personId in peopleArray)
{
ABRecordRef person = (__bridge ABRecordRef) personId;
//process other attributes of the address book
ABMultiValueRef multi = ABRecordCopyValue(person, kABPersonAddressProperty);
CFArrayRef addressRef = ABMultiValueCopyArrayOfAllValues(multi);
NSArray *addressArray = [(__bridge NSArray *) addressRef copy];
for(NSDictionary *address in addressArray)
{
//process the addresses
}
CFRelease(addressRef);
addressRef = nil;
}
From what I've researched on the internet and in Apple's Memory Management guides, this looks like the proper way to do it. The problem is when I got to run the code, it halts on "CFRelease(addressRef)", highlighted green with text "Thread 1" (not sure what this error means). I've also tried putting the CFRelease before the for loop, but the same issue occurs.
If I remove the CFRelease, it compiles, but there is a memory leak at the creation of addressArray. Does anyone know how to solve this problem? I can't seem to figure it out using ARC.
Instead of NSArray *peopleArray =[(__bridge NSArray *) arrayRef copy]; CFRelease(arrayRef);, use NSArray *peopleArray = CFBridgingRelease(arrayRef). This transfers ownership of the object to ARC.
Whenever you see "Copy" in a method name you should use (__bridge_transfer <ObjectType> *)
Then ARC will be responsible for releasing the object.
So your code will look like:
NSArray *peopleArray = (__bridge_transfer NSArray *)ABAddressBookCopyArrayOfAllPeople(addressBook);
In my app, static analyser points a leak in the following code:
ABMultiValueRef phone = (NSString *)ABRecordCopyValue(person, kABPersonPhoneProperty);
NSString *mobilephone= (NSString*)ABMultiValueCopyValueAtIndex (phone,0);
similarly whenever i use this function ABRecordCopyValue it points a leak
I tried to release it by [phone release]; method, however I am getting a compiler warning "invalid receiver type 'abmultivalueref'". What is the proper way to release this ?
It looks like you are confusing the NS data types with the CF data types. The address book methods typically return core foundation (CF) objects. These objects are toll-free bridged, which means they can be used interchangeably with NS types.
When using core foundation objects, any method with 'copy' in its name will return an object that you later need to release using CFRelease. Only if you cast it to its NS equivalent can you use - release.
So your code could be written as either of the following:
ABMultiValueRef phone = ABRecordCopyValue(person, kABPersonPhoneProperty);
NSString *mobilephone = (NSString *)ABMultiValueCopyValueAtIndex(phone, 0);
// other code
[mobilephone release];
or
ABMultiValueRef phone = ABRecordCopyValue(person, kABPersonPhoneProperty);
CFStringRef mobilephone = ABMultiValueCopyValueAtIndex(phone, 0);
// other code
CFRelease(mobilephone);
Have you tried with CFRelease(phone); ?
Because ABMultiValueCopyValueAtIndex is not a NSString, it's a CFStringRef
Using __bridge_transfer ensures that ARC will release the object for you.
Using __bridge means you must release the returned object manually.
In the documentation for MKReverseGeocoder, the administrativeArea property gives you the current state the user is in, and it mentions in an example that it returns EITHER the state name OR its abbreviation. I am wondering if anyone knows how to get the abbreviation instead of the full state name...I have been able to find nothing that shows this is even a possibility besides that brief example that doesn't mention HOW.
Thanks!
I also needed to convert the State field from MKReverseGeocoder into a two letter abbreviation, so I created this plist:
https://github.com/djibouti33/US-State-Abbreviations
Here's how I use it:
// in my init
NSString *plistPath = [[NSBundle mainBundle] pathForResource:#"USStateAbbreviations" ofType:#"plist"];
self.usStateAbbreviations = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
// MKReverseGeocoder delegate method
- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFindPlacemark:(MKPlacemark *)placemark {
...
NSString *state = [address objectForKey:#"State"];
NSString *stateAbbreviation = [self.usStateAbbreviations objectForKey:[state uppercaseString]];
NSString *stateTarget = state;
if (stateAbbreviation) {
stateTarget = stateAbbreviation;
}
...
}
There is no way to do this. Not sure why the Apple docs say "CA or California".
It's easy to convert state to 2 letter name. Just create a plist (table, or NSDictionary works too) of the following: http://www.usps.com/ncsc/lookups/usps_abbreviations.html and use that to look up the 2 letter abbreviations.