Reduce time to perform big operation on iphone app - iphone

When I launch an app, I need to retrieve all contacts from Address Book & store it in array to display it in table view. I wrote this code in viewDidAppear method. While contacts are retrieving from Address Book , I am showing activity indicator. I have around 1100 contacts in my address book. For this it took 14 seconds to retrieve data & store it in array. Its not acceptable time. So I need to optimize this & reduce time to max 2 to 3 seconds.
I need all contacts because when my app launches , I need to search for contacts so I need all the data available in my array.
How can I reduce this timing ? If you need more information just let me know.
Any kind of help is highly appreciated. Thanks.
UPDATE 1 : My code
- (NSMutableArray*)getAddressBookData {
self.tempArray = [[NSMutableArray alloc]init];
addressBook = ABAddressBookCreate();
APP_DELGATE.people = (NSArray*)ABAddressBookCopyArrayOfAllPeople(addressBook);
peopleCount = [APP_DELGATE.people count];
for (int i=0; i<peopleCount; i++) {
ABRecordRef record = [APP_DELGATE.people objectAtIndex:i];
NSNumber *recordId = [NSNumber numberWithInteger:ABRecordGetRecordID(record)];
NSLog(#"record id is %#",recordId);
// Get fname, lname, company
NSString *fnm = (NSString *)ABRecordCopyValue(record, kABPersonFirstNameProperty) ;
NSString *lnm = (NSString *)ABRecordCopyValue(record, kABPersonLastNameProperty) ;
NSString *comp = (NSString*)ABRecordCopyValue(record,kABPersonOrganizationProperty);
// Get Ph no
ABMultiValueRef phoneNumberProperty = ABRecordCopyValue(record, kABPersonPhoneProperty);
NSArray* phoneNumbers = [self getPhoneNoWithoutSymbols:(NSArray*)ABMultiValueCopyArrayOfAllValues(phoneNumberProperty)];
NSString *strPhoneNos = [self getStringRepresentaionFromArray:phoneNumbers];
NSLog(#"strPhoneNos => %#",strPhoneNos);
// Get emails
ABMultiValueRef emailProperty = ABRecordCopyValue(record, kABPersonEmailProperty);
NSArray* emails = (NSArray*)ABMultiValueCopyArrayOfAllValues(emailProperty);
NSString *strEmails = [self getStringRepresentaionFromArray:emails];
NSLog(#"strEmails => %#",strEmails);
// Get URL
ABMultiValueRef urlProperty = ABRecordCopyValue(record, kABPersonURLProperty);
NSArray* urls = (NSArray*)ABMultiValueCopyArrayOfAllValues(urlProperty);
NSString *strURLs = [self getStringRepresentaionFromArray:urls];
NSLog(#"strURLs => %#",strURLs);
// Get Address
ABMultiValueRef address=ABRecordCopyValue(record, kABPersonAddressProperty);
CFDictionaryRef dic=nil;
NSMutableArray *addressArray = [[NSMutableArray alloc]init];
for (int index=0; index<ABMultiValueGetCount(address); index++) {
dic=ABMultiValueCopyValueAtIndex(address, index);
NSString* labelName=(NSString*)ABMultiValueCopyLabelAtIndex(address, index);
if (labelName) {
NSString *street =(NSString*) CFDictionaryGetValue(dic, kABPersonAddressStreetKey);
NSString *city= (NSString*)CFDictionaryGetValue(dic, kABPersonAddressCityKey) ;
NSString *state= CFDictionaryGetValue(dic, kABPersonAddressStateKey);
NSString *country=CFDictionaryGetValue(dic, kABPersonAddressCountryKey);
NSString *zipcode=CFDictionaryGetValue(dic, kABPersonAddressZIPKey);
NSString *addressDetails=#"";
if (street) {
addressDetails=[NSString stringWithFormat:#"%# ",street];
}
if (city) {
addressDetails=[NSString stringWithFormat:#"%# %# ",addressDetails,city];
}
if (state) {
addressDetails=[NSString stringWithFormat:#"%# %# ",addressDetails,state];
}
if (country) {
addressDetails=[NSString stringWithFormat:#"%# %# ",addressDetails,country];
}
if (zipcode) {
addressDetails=[NSString stringWithFormat:#"%# %# ",addressDetails,zipcode];
}
[addressArray addObject:addressDetails];
}
}
NSString *strAddress = [self getStringRepresentaionFromArray:addressArray];
NSLog(#"strAddress => %#",strAddress);
// Get Notes
NSString *noteString=(NSString *)ABRecordCopyValue(record, kABPersonNoteProperty);
// Get Birthdate
NSDate *birthDate=(NSDate*)ABRecordCopyValue(record, kABPersonBirthdayProperty) ;
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"MMMM dd yyyy"];
NSString *birthdateString = [formatter stringFromDate:birthDate];
[formatter release];
// Get user image
UIImage *image = nil;
if( ABPersonHasImageData( record ) ) {
NSData *imageData = (NSData*)ABPersonCopyImageData(record);
image = [UIImage imageWithData:imageData];
[imageData release];
}
// Create User object & add it to array
User *user = [[User alloc]initUserWithUniqID:recordId.intValue FirstName:fnm lastName:lnm company:comp phoneNumbers:strPhoneNos emails:strEmails urls:strURLs address:strAddress notes:noteString dob:birthdateString userImage:image];
[self.tempArray addObject:user];
[user release];
}
self.tempArray = [NSMutableArray arrayWithArray:[self.tempArray sortedArrayUsingSelector:#selector(compare:)]];
APP_DELGATE.allUsersArray = self.tempArray;
return tempArray;
}
-(NSMutableArray*)getPhoneNoWithoutSymbols:(NSArray*)array {
self.phNoArray = [[NSMutableArray alloc]init];
for (NSString *str in array) {
[self.phNoArray addObject:[self getPhNo:str]];
}
return self.phNoArray;
}
-(NSString*)getPhNo:(NSString*)str {
NSString *str0 = [str stringByReplacingOccurrencesOfString:#" " withString:#""];
NSString *str1 = [str0 stringByReplacingOccurrencesOfString:#"(" withString:#""];
NSString *str2 = [str1 stringByReplacingOccurrencesOfString:#")" withString:#""];
NSString *str3 = [str2 stringByReplacingOccurrencesOfString:#"-" withString:#""];
return str3;
}
-(NSString*)getStringRepresentaionFromArray:(NSArray*)array {
return [array componentsJoinedByString:DELIMITER_SYMBOL];
}

Firstly, try some general approaches to reduce time by using more optimized code and less repetition of code and also through less use of loops or may be only iterate loops only till the data is obtained. Also you can check whether your code is properly distributed as far as the Time Profiling is concerned.
Secondly, we feel that time required is more because user is shown an Activity Indicator till 14 seconds. If you don't show it and don't block the User Interface till data is getting copied into your array, then user may feel that it is more smooth So here is how you can do that:
You can use NSThread to allow the application to launch while you retrieve all your data from the AddressBook and Store it in your array.
You can use
[NSThread detachNewThreadSelector:#selector(fetchAddressContacts:) withObject:nil];
For more information you can refer to detachNewThreadSelector: from NSThread
So basically in AppDelegate you need to code as following in AppDelegate.m
-(void)applicationDidFinishLaunching:(UIApplication *)application {
[NSThread detachNewThreadSelector:#selector(fetchAddressContacts) withObject:nil];
}
-(void)fetchAddressContacts {
//Do your stuff to copy your contacts from address book to array
// Once you finish this task you can trigger an NSNotification which could be caught on some other viewController or implement a delegate to transfer data to a viewController and proper actions can be executed once the data is loaded.
}
Let me know if you need more help.
Hope this helps you.

Perform your contacts retrieval method diagnostics and identify what calls are causing the bottleneck here.
Share your code and describe the bottlenecks
Any operation taking serious amount of time should be performed in a thread. In this case, I'd gather the data on first startup only and store them internally. You can refresh the primary data anytime later on demand (refresh button?) otherwise work with the internally stored "clone" of the contacts rather than calling any time-consuming operation on every app's start.
BTW: Be careful about the address book contacts these days :-)

When your app first install fetch all the contacts and save in coredate/plist file when next time app opens then show the contacts from the storage which is very fast as compare to fetching contacts from iPhone database. Now the problem how to update contacts which newly added, so for every contact there is property called "kABPersonModificationDateProperty" and "kABPersonCreationDateProperty" ,use this to find only updated contacts and add in the storage. Hope this helps.

Related

Performance issue creating Section Index Titles for UITableView

I'm displaying an array of contacts ( [[ContactStore sharedStore]allContacts] ) in a tableview and have divided the list into alphabetic sections. I have used the following code to return an array of the first letters of the contacts, and a dictionary of the number of entries per letter.
//create an array of the first letters of the names in the sharedStore
nameIndex = [[NSMutableArray alloc] init];
//create a dictionary to save the number of names for each first letter
nameIndexCount = [[NSMutableDictionary alloc]init];
for (int i=0; i<[[[ContactStore sharedStore]allContacts]count]; i++){
//Get the first letter and the name of each person
Contact *p = [[[ContactStore sharedStore]allContacts]objectAtIndex:i];
NSString *lastName = [p lastName];
NSString *alphabet = [lastName substringToIndex:1];
//If that letter is absent from the dictionary then add it and set its value as 1
if ([nameIndexCount objectForKey:alphabet] == nil) {
[nameIndex addObject:alphabet];
[nameIndexCount setValue:#"1" forKey:alphabet];
//If its already present add one to its value
} else {
NSString *newValue = [NSString stringWithFormat:#"%d", ([[nameIndexCount valueForKey:alphabet] intValue] + 1)];
[nameIndexCount setValue:newValue forKey:alphabet];
}
}
This works, however it is very slow when the array is large, I'm sure there's a better way to do this but I'm quite new to this so am not sure how. Are there any suggestions for a better way to do this?
Although Bio Cho has a good point, you might see an increase in performance by calling
[[ContactStore sharedStore]allContacts]
only once. For example:
nameIndex = [[NSMutableArray alloc] init];
nameIndexCount = [[NSMutableDictionary alloc] init];
/*
Create our own copy of the contacts only once and reuse it
*/
NSArray* allContacts = [[ContactStore sharedStore] allContacts];
for (int i=0; i<[allContacts count]; i++){
//Get the first letter and the name of each person
Contact *p = allContacts[i];
NSString *lastName = [p lastName];
NSString *alphabet = [lastName substringToIndex:1];
//If that letter is absent from the dictionary then add it and set its value as 1
if ([nameIndexCount objectForKey:alphabet] == nil) {
[nameIndex addObject:alphabet];
[nameIndexCount setValue:#"1" forKey:alphabet];
//If its already present add one to its value
} else {
NSString *newValue = [NSString stringWithFormat:#"%d", ([[nameIndexCount
valueForKey:alphabet] intValue] + 1)];
[nameIndexCount setValue:newValue forKey:alphabet];
}
}
Though I can't say for sure, I'd guess that repeatedly accessing your shared store is what's killing you. Maybe only accessing it once will give you what you need.
Consider storing your contacts in Core Data and using an NSFetchedResultsController.
The NSFetchedResultsController will only load a subset of the rows which are visible on the table view, thus preventing your user from having to wait for all the contacts to be sorted.
NSFetchedResultsController will also sort your contacts by an attribute (ie. first or last name), and you can set your section titles to be the first letter of the field you're sorting by.
Take a look at this question and this tutorial.

How to get original special characters from SQLite db using iPhone SDK?

I am inserting HTML content (which has special characters like bullets, etc) into the SQLite database.
When I try to get the content on a view, it does not show the special characters correctly. It shows me junk text.
How can I ensure that whatever text I insert in database, it is displayed correctly on the view.
Thanks!
My Insertion code:
// This query method implementation is in different file
- (NSArray *)executeQuery:(NSString *)sql arguments:(NSArray *)args {
sqlite3_stmt *sqlStmt;
if (![self prepareSql:sql inStatament:(&sqlStmt)])
return nil;
int i = 0;
int queryParamCount = sqlite3_bind_parameter_count(sqlStmt);
while (i++ < queryParamCount)
[self bindObject:[args objectAtIndex:(i - 1)] toColumn:i inStatament:sqlStmt];
NSMutableArray *arrayList = [[NSMutableArray alloc] init]; // By Devang
int columnCount = sqlite3_column_count(sqlStmt);
while ([self hasData:sqlStmt]) {
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
for (i = 0; i < columnCount; ++i) {
id columnName = [self columnName:sqlStmt columnIndex:i];
id columnData = [self columnData:sqlStmt columnIndex:i];
[dictionary setObject:columnData forKey:columnName];
}
[arrayList addObject:dictionary];
//[arrayList addObject:[dictionary autorelease]];
}
sqlite3_finalize(sqlStmt);
return arrayList;
}
// now call this method by make object for this file
NSString *inserQuery =[NSString stringWithFormat:#"insert into feedtest (title,summary,image,id) values ('%#','%#','%#',%d)",cell.textLabel.text,source,returnURL,indexPath.row];
NSLog(#"query - %#",inserQuery);
[database executeQuery:inserQuery];
// Retrive the data
NSString *sd=[NSString stringWithFormat:#"Select title,summary from feedtest"];
NSMutableArray *p=[[NSMutableArray alloc]init];
p=[[database executeQuery:sd ] mutableCopy];
[database close];
NSString *titleHTML = [[p objectAtIndex:i]valueForKey:#"title"];
NSString *postHTML =[[p objectAtIndex:i]valueForKey:#"summary"];
NSLog(#"%#",titleHTML);
NSLog(#"%#",postHTML);
You can check your local database using FireFox plugin SQLite. But, sometimes on retrieving we faced strange problem like what is present in the storage not coming properly and sometime, there is crash. So my suggestion is what you should check encoding scheme(normally, it's not matter more) and while getting data use this:
[NSString stringWithFormat:#"%s",(const char*)sqlite3_column_text(statement, 4)] ;
instead of:
[NSString stringWithUTF8String:(const char*)sqlite3_column_text(statement, 4)];
Hope, this is what you're looking for. Any concern get back to me. :)

How to get contacts detail of iphone and make CSV file of that contact

I want to get contact details in an iPhone with information like First Name, Last Name, Phone Number, Phone Number Type, Email Address, Email Address Type etc..
Can anyone help me with that?
I want to make a .csv file out of the contact details in a particular iPhone. I want to fetch iPhone address book data.
Following is the code to get all informations of iPhone contact book...
-(void)collectContacts
{
NSMutableDictionary *myAddressBook = [[NSMutableDictionary alloc] init];
ABAddressBookRef addressBook = ABAddressBookCreate();
CFArrayRef people = ABAddressBookCopyArrayOfAllPeople(addressBook);
for(int i = 0;i<ABAddressBookGetPersonCount(addressBook);i++)
{
ABRecordRef ref = CFArrayGetValueAtIndex(people, i);
// Get First name, Last name, Prefix, Suffix, Job title
NSString *firstName = (NSString *)ABRecordCopyValue(ref,kABPersonFirstNameProperty);
NSString *lastName = (NSString *)ABRecordCopyValue(ref,kABPersonLastNameProperty);
NSString *prefix = (NSString *)ABRecordCopyValue(ref,kABPersonPrefixProperty);
NSString *suffix = (NSString *)ABRecordCopyValue(ref,kABPersonSuffixProperty);
NSString *jobTitle = (NSString *)ABRecordCopyValue(ref,kABPersonJobTitleProperty);
[myAddressBook setObject:firstName forKey:#"firstName"];
[myAddressBook setObject:lastName forKey:#"lastName"];
[myAddressBook setObject:prefix forKey:#"prefix"];
[myAddressBook setObject:suffix forKey:#"suffix"];
[myAddressBook setObject:jobTitle forKey:#"jobTitle"];
NSMutableArray *arPhone = [[NSMutableArray alloc] init];
ABMultiValueRef phones = ABRecordCopyValue(ref, kABPersonPhoneProperty);
for(CFIndex j = 0; j < ABMultiValueGetCount(phones); j++)
{
CFStringRef phoneNumberRef = ABMultiValueCopyValueAtIndex(phones, j);
NSString *phoneLabel =(NSString*) ABAddressBookCopyLocalizedLabel (ABMultiValueCopyLabelAtIndex(phones, j));
NSString *phoneNumber = (NSString *)phoneNumberRef;
NSMutableDictionary *temp = [[NSMutableDictionary alloc] init];
[temp setObject:phoneNumber forKey:#"phoneNumber"];
[temp setObject:phoneLabel forKey:#"phoneNumber"];
[arPhone addObject:temp];
[temp release];
}
[myAddressBook setObject:arPhone forKey:#"Phone"];
[arPhone release];
CFStringRef address;
CFStringRef label;
ABMutableMultiValueRef multi = ABRecordCopyValue(ref, kABPersonAddressProperty);
for (CFIndex i = 0; i < ABMultiValueGetCount(multi); i++)
{
label = ABMultiValueCopyLabelAtIndex(multi, i);
CFStringRef readableLabel = ABAddressBookCopyLocalizedLabel(label);
address = ABMultiValueCopyValueAtIndex(multi, i);
CFRelease(address);
CFRelease(label);
}
ABMultiValueRef emails = ABRecordCopyValue(ref, kABPersonEmailProperty);
NSMutableArray *arEmail = [[NSMutableArray alloc] init];
for(CFIndex idx = 0; idx < ABMultiValueGetCount(emails); idx++)
{
CFStringRef emailRef = ABMultiValueCopyValueAtIndex(emails, idx);
NSString *strLbl = (NSString*) ABAddressBookCopyLocalizedLabel (ABMultiValueCopyLabelAtIndex (emails, idx));
NSString *strEmail_old = (NSString*)emailRef;
NSMutableDictionary *temp = [[NSMutableDictionary alloc] init];
[temp setObject:strEmail_old forKey:#"strEmail_old"];
[temp setObject:strLbl forKey:#"strLbl"];
[arEmail addObject:temp];
[temp release];
}
[myAddressBook setObject:arEmail forKey:#"Email"];
[arEmail release];
}
[self createCSV:myAddressBook];
}
-(void) createCSV :(NSMutableDictionary*)arAddressData
{
NSMutableString *stringToWrite = [[NSMutableString alloc] init];
[stringToWrite appendString:[NSString stringWithFormat:#"%#,",[arAddressData valueForKey:#"firstName"]]];
[stringToWrite appendString:[NSString stringWithFormat:#"%#,",[arAddressData valueForKey:#"lastName"]]];
[stringToWrite appendString:[NSString stringWithFormat:#"%#,",[arAddressData valueForKey:#"jobTitle"]]];
//[stringToWrite appendString:#"fname, lname, title, company, phonetype1, value1,phonetype2,value,phonetype3,value3phonetype4,value4,phonetype5,value5,phonetype6,value6,phonetype7,value7,phonetype8,value8,phonetype9,value9,phonetype10,value10,email1type,email1value,email2type,email2value,email3type,email3‌​value,email4type,email4value,email5type,email5value,website1,webs‌​ite2,website3"];
NSMutableArray *arPhone = (NSMutableArray*) [arAddressData valueForKey:#"Phone"];
for(int i = 0 ;i<[arPhone count];i++)
{
NSMutableDictionary *temp = (NSMutableDictionary*) [arPhone objectAtIndex:i];
[stringToWrite appendString:[NSString stringWithFormat:#"%#,",[temp valueForKey:#"phoneNumber"]]];
[stringToWrite appendString:[NSString stringWithFormat:#"%#,",[temp valueForKey:#"phoneNumber"]]];
[temp release];
}
NSArray *paths=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *documentDirectory=[paths objectAtIndex:0];
NSString *strBackupFileLocation = [NSString stringWithFormat:#"%#/%#", documentDirectory,#"ContactList.csv"];
[stringToWrite writeToFile:strBackupFileLocation atomically:YES encoding:NSUTF8StringEncoding error:nil];
}
I used iApple's code above as a starting point and created a working version from it - this one just collects all address book entries in an array. As mentioned above the original iApple doesn't work, there's a few bugs in it. This one works, and was tested.
Note: This doesn't return any contacts that don't have a name set - you can remove that for your own code, I just did it because I only need contacts with names set, and NSMutableDictionary doesn't like nil entries (crashes).
In my own address book I have a few entries that are just an email - I am not sure how they got there, but it's certainly possible to have address book entries without a name. Keep that in mind when iterating over an address book.
I am using the full name as per Apple's recommendations - ABRecordCopyCompositeName returns a composite of first and last name in the order specified by the user.
Finally, I made this a static method and put it in a helper class.
This is for use with ARC!
// returns an array of dictionaries
// each dictionary has values: fullName, phoneNumbers, emails
// fullName is a string
// phoneNumbers is an array of strings
// emails is an array of strings
+ (NSArray *)collectAddressBookContacts {
NSMutableArray *allContacts = [[NSMutableArray alloc] init];
ABAddressBookRef addressBook = ABAddressBookCreate();
CFArrayRef people = ABAddressBookCopyArrayOfAllPeople(addressBook);
for(int i = 0;i<ABAddressBookGetPersonCount(addressBook);i++)
{
NSMutableDictionary *aPersonDict = [[NSMutableDictionary alloc] init];
ABRecordRef ref = CFArrayGetValueAtIndex(people, i);
NSString *fullName = (__bridge NSString *) ABRecordCopyCompositeName(ref);
if (fullName) {
[aPersonDict setObject:fullName forKey:#"fullName"];
// collect phone numbers
NSMutableArray *phoneNumbers = [[NSMutableArray alloc] init];
ABMultiValueRef phones = ABRecordCopyValue(ref, kABPersonPhoneProperty);
for(CFIndex j = 0; j < ABMultiValueGetCount(phones); j++) {
NSString *phoneNumber = (__bridge NSString *) ABMultiValueCopyValueAtIndex(phones, j);
[phoneNumbers addObject:phoneNumber];
}
[aPersonDict setObject:phoneNumbers forKey:#"phoneNumbers"];
// collect emails - key "emails" will contain an array of email addresses
ABMultiValueRef emails = ABRecordCopyValue(ref, kABPersonEmailProperty);
NSMutableArray *emailAddresses = [[NSMutableArray alloc] init];
for(CFIndex idx = 0; idx < ABMultiValueGetCount(emails); idx++) {
NSString *email = (__bridge NSString *)ABMultiValueCopyValueAtIndex(emails, idx);
[emailAddresses addObject:email];
}
[aPersonDict setObject:emailAddresses forKey:#"emails"];
// if you want to collect any other info that's stored in the address book, it follows the same pattern.
// you just need the right kABPerson.... property.
[allContacts addObject:aPersonDict];
} else {
// Note: I have a few entries in my phone that don't have a name set
// Example one could have just an email address in their address book.
}
}
return allContacts;
}
First you will need to use the address book framework so this must be added to your Xcode project.
Next you will need to break the task down into a couple steps.
1) Get the people inside the address book
2) Create your .csv file. I'm assuming you know something about CSV file formatting using characters to separate fields and when to add return characters so you have a properly formatted file. This is probably left for another question thread if you need help with this.
3) Save your .csv file somewhere
1) To get an array of all people in the address book you would do something like the following. The reference documentation for ABAddressBook is here. It should be very helpful in helping you access the data.
ABAddressBook *sharedBook = [ABAddressBook sharedAddressBook];
NSArray *peopleList = [sharedBook people];
2) You will have to iterate through each of the people and build your overall csv data. Usually you would manually create the csv data in an NSString and then convert it to NSData and save the NSData to a file. This is not ideal if you are dealing with a really large set of data. If this is the case then you would probably want some code to write your csv data to the file in chunks so you can free memory as you go. For simplicity sake my code just shows you creating the full file then saving the whole works.
NSString *csvString = #"";
for(ABPerson *aPerson in peopleList) {
//Do something here to write each property you want to the CSV file.
csvString = [csvString stringByAppendingFormat:#"'%#',"
[aPerson valueForProperty:kABFirstNameProperty]];
}
NSData *csvData = [csvString dataUsingEncoding:NSUTF8StringEncoding];
3) Write you file to somewhere
//This is an example of writing your csv data to a file that will be saved in the application's sand box directory.
//This file could be extracted using iTunes file sharing.
//Get the proper path to save the file
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writablePath = [documentsDirectory stringByAppendingPathComponent:#"my_file.csv"];
//Actually write the data
BOOL isSuccessful = [csvData writeToFile:fullPath atomically:NO];
if(isSuccessful) {
//Do something if the file was written
} else {
//Do something if there was an error writing the file
}
See Adress Book API particulary Importing and Exporting Person and Group Records
chack also the Address Book Test example in this blog

Writing an NSMutableArray to my documents directory fails

I am attempting to cache a web request. Basically I have an app that uses a facebook user's friend list but I don't want to grab it every single time they log in. Maybe refresh once per month. Caching the friend list in a plist in the documents directory seems to make sense for this functionality. I do this as follows:
- (void)writeToDisk {
NSLog(#"writing cache to disk, where cache = %#", cache);
BOOL res = [cache writeToFile:[FriendCache persistentPath] atomically:YES];
NSLog(#"reading cache from disk immediately after writing, res = %d", res);
NSMutableArray *temp = [[NSMutableArray alloc] initWithContentsOfFile:[FriendCache persistentPath]];
NSLog(#"cache read in = %#", temp);
}
+ (NSString *)persistentPath {
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
return [documentsDirectory stringByAppendingPathComponent:#"FriendCache.plist"];
}
These are members of a FriendCache singleton I am using which basically wraps an NSMutableArray. I have verified that the peristentPath method is returning a valid path. As you you can see in the writeToDisk method, I verify there is data in the cache and then I print the result of the write and check if any data could be read back in. There is never data read back in, because the result of the file write is 0.
The output of the cache print is very long, but here is the abbreviated version:
2010-12-28 13:35:23.006 AppName[51607:207] writing cache to disk, where cache = (
{
birthday = "<null>";
name = "Some Name1";
pic = "http://profile.ak.fbcdn.net/hprofile-ak-snc4/hs1324.snc4/7846385648654.jpg";
"pic_big" = "http://profile.ak.fbcdn.net/hprofile-ak-snc4/hs442.snc4/784365789465746.jpg";
"pic_square" = "http://profile.ak.fbcdn.net/hprofile-ak-snc4/hs1324.snc4/7846357896547.jpg";
sex = female;
status = "<null>";
uid = 892374897165;
},
{
birthday = "<null>";
name = "Some Name2";
pic = "http://profile.ak.fbcdn.net/hprofile-ak-snc4/hs625.ash1/54636536547_s.jpg";
"pic_big" = "http://profile.ak.fbcdn.net/hprofile-ak-snc4/hs170.ash2/65465656365666_n.jpg";
"pic_square" = "http://profile.ak.fbcdn.net/hprofile-ak-snc4/hs625.ash1/654635656547_q.jpg";
sex = female;
status = "<null>";
uid = 7658436;
},
...
One thing I checked out is when using writeToFile, I must make sure the object I am writing has valid plist objects. I did check this and here is how I construct the cache object:
- (void)request:(FBRequest*)request didLoad:(id)result{
NSMutableArray *friendsInfo = [[[NSMutableArray alloc] init] autorelease];
for (NSDictionary *info in result) {
NSString *friend_id = [NSString stringWithString:[[info objectForKey:#"uid"] stringValue]];
NSString *friend_name = nil;
NSString *friend_sex = nil;
NSString *friend_relationship_status = nil;
NSString *friend_current_location = nil;
if ([info objectForKey:#"name"] != [NSNull null]) {
friend_name = [NSString stringWithString:[info objectForKey:#"name"]];
}
if ([info objectForKey:#"relationship_status"] != [NSNull null]) {
friend_relationship_status = [NSString stringWithString:[info objectForKey:#"relationship_status"]];
}
if ([info objectForKey:#"sex"] != [NSNull null]) {
friend_sex = [NSString stringWithString:[info objectForKey:#"sex"]];
}
if ([info objectForKey:#"current_location"] != [NSNull null]) {
friend_current_location = [[info objectForKey:#"current_location"] objectForKey:#"name"];
}
NSString *friend_pic_square = [info objectForKey:#"pic_square"];
NSString *friend_status = [info objectForKey:#"status"];
NSString *friend_pic = [info objectForKey:#"pic"];
NSString *friend_pic_big = [info objectForKey:#"pic_big"];
NSString *friend_birthday = [info objectForKey:#"birthday"];
NSDictionary *friend_info = [NSDictionary dictionaryWithObjectsAndKeys:
friend_id,#"uid",
friend_name, #"name",
friend_pic_square, #"pic_square",
friend_status, #"status",
friend_sex, #"sex",
friend_pic, #"pic",
friend_pic_big, #"pic_big",
friend_birthday, #"birthday",
friend_relationship_status, #"relationship_status",
friend_current_location, #"current_location",
nil];
// If the friend qualifies as a single of your gender, add to the friend cache
if ( [AppHelpers friendQualifies:friend_info] == YES) {
[[FriendCache sharedInstance] push:friend_info];
}
}
[[FriendCache sharedInstance] writeToDisk];
}
My push method just wraps the NSMutableArray push:
- (void)push:(id)o {
[cache addObject:o];
}
Can you think of any reason why the write would fail?
Thanks!
So as we already pointed out, it's because of the usage of the NSNull objects.
The best way to avoid this is to create an object Friend, with all of the needed properties. Then you can easily set nil values, something not possible with NSDictionary objects (well, you'd have to remove the key, which is not very good practice).
Then, by implementing the NSCoding protocol, you can easily archive (serialize) your custom object.
This is a much better way of handling your data, and it will become MUCH easier in the future. You'll be able to call messages on the Friend objects, something not possible with NSDictionary.
Use NSError-aware API for NSPropertyListSerialization to get the data and the NSData NSError aware write API so you get a meaningful error helping you understand what your problem might be.

ABAddressBook store values in NSDictionary

I have an app that displays ABAddressBook contacts in a UITableView. Currently I'm reading the contacts into an NSDictionary, however this appears to crash for some users, which I suspect is a memory issue.
Is there another approach to display ABAddressBook contacts in a UITableView without either first storing them in an NSDictionary or using ABPeoplePicker?
A different way using ARC:
ABAddressBookRef addressBook = ABAddressBookCreate();
CFArrayRef addressBookData = ABAddressBookCopyArrayOfAllPeople(addressBook);
CFIndex count = CFArrayGetCount(addressBookData);
NSMutableArray *contactsArray = [NSMutableArray new];
for (CFIndex idx = 0; idx < count; idx++) {
ABRecordRef person = CFArrayGetValueAtIndex(addressBookData, idx);
NSString *firstName = (__bridge_transfer NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty);
if (firstName) {
NSDictionary *dict = [NSDictionary dictionaryWithObject:firstName ForKey:#"name"];
[contactsArray addObject:dict];
}
}
CFRelease(addressBook);
CFRelease(addressBookData);
You can use following way,
ABAddressBookRef ab = ABAddressBookCreateWithOptions(NULL, NULL);
NSArray *arrTemp = (NSArray *)ABAddressBookCopyArrayOfAllPeople(ab);
The above 2 lines will create an array for all your contacts on the iPhone.
Now whatever property of a contact you want to display you can display by using the below code. For example, I want to display the first name of all contacts and then create one Mutable array called it arrContact.
NSMutableArray *arrContact = [[NSMutableArray alloc] init];
for (int i = 0; i < [arrTemp count]; i++)
{
NSMutableDictionary *dicContact = [[NSMutableDictionary alloc] init];
NSString *str = (NSString *) ABRecordCopyValue([arrTemp objectAtIndex:i], kABPersonFirstNameProperty);
#try
{
[dicContact setObject:str forKey:#"name"];
}
#catch (NSException * e) {
[dicContact release];
continue;
}
[arrContact addObject:dicContact];
[dicContact release];
}
Now just display it using the arrContact array in a table view..
Same as Abizern's answer, but if you want to display full names that are localized, use ABRecordCopyCompositeName. (In English names are "First Last", but in Chinese names are "LastFirst").
ABRecordRef person = CFArrayGetValueAtIndex(addressBookData, idx);
NSString *fullName = (__bridge_transfer NSString *)ABRecordCopyCompositeName(person);//important for localization