plists are wonderful for storing small amounts of semi-structured data when you don’t want the overhead of using a full-blown database. OS X, as you have no doubt noticed, uses plists extensively to store configuration data.
I like to use plists to create configuration driven applications. On the App Store you can find TRIBE and The Green Book. These two application look very different, but they use exactly the same code base. The difference is a couple of plist configuration files. Our client is very happy with this, because they are busy churning out many different titles for the App Store using this method.
Reading a plist file from the application bundle just requires a few lines of code, and some error handling. I like to place that code in a nice convenience method like this:
- (id)readPlist:(NSString *)fileName { NSData *plistData; NSString *error; NSPropertyListFormat format; id plist; NSString *localizedPath = [[NSBundle mainBundle] pathForResource:fileName ofType:@"plist"]; plistData = [NSData dataWithContentsOfFile:localizedPath]; plist = [NSPropertyListSerialization propertyListFromData:plistData mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&error]; if (!plist) { NSLog(@"Error reading plist from file '%s', error = '%s'", [localizedPath UTF8String], [error UTF8String]); [error release]; } return plist; }
I’m not too fond of using id as return values or parameters to methods. I prefer stronger type checks, so I typically wrap the readPlist method in two methods that return either an array or a dictionary.
- (NSArray *)getArray:(NSString *)fileName { return (NSArray *)[self readPlist:fileName]; } - (NSDictionary *)getDictionary:(NSString *)fileName { return (NSDictionary *)[self readPlist:fileName]; }
Writing to a plist file is not much more difficult:
- (void)writePlist:(id)plist fileName:(NSString *)fileName { NSData *xmlData; NSString *error; NSString *localizedPath = [[NSBundle mainBundle] pathForResource:fileName ofType:@"plist"]; xmlData = [NSPropertyListSerialization dataFromPropertyList:plist format:NSPropertyListXMLFormat_v1_0 errorDescription:&error]; if (xmlData) { [xmlData writeToFile:localizedPath atomically:YES]; } else { NSLog(@"Error writing plist to file '%s', error = '%s'", [localizedPath UTF8String], [error UTF8String]); [error release]; } }
Note that writing to a file inside the app bundle is not good if you want your data to stick around after the application is upgraded since the bundle will be overwritten. You can look at the Apple’s SQLite code examples for code snippets on how to copy files out of the bundle before using them.
March 9th, 2009 at 00:14
Do you ever plan on doing any SQL tutorials?
March 9th, 2009 at 06:54
@Oliva: I have a couple of SQLite posts in the pipeline. Stay tuned.
June 26th, 2009 at 08:19
Nick,
Thanks, nice, quick solution, well written, straight-and-to-the-point like I like it.
Cheers, Eric.
August 23rd, 2009 at 00:07
Nick,
Excellent and precise article. Good work.
Ravi
July 15th, 2011 at 12:35
I get an error on the first row;
– (id)readPlist:(NSString *)fileName {
“Did you mean readLink”
Any idea?
😉
August 18th, 2011 at 01:35
You don’t have to write so much code, look into dictionaryWithContentsOfFile: or arrayWithContentsOfFile:
December 22nd, 2011 at 06:40
Note that you cannot write over a plist file stored in your Bundle the way this method does… you don’t have that write permission. Strangely, if you use these methods in your app, during a given run of the app it will look to you like you have written over a plist file in your Bundle… you can change a value and save it, and read it back in as the new value. HOWEVER… run the app again (or look in the plist file) and it is as if you haven’t written to the file at all. And you haven’t! So I really don’t understand that particular behavior (why it appears to have worked), yet. If you really want to save data to a plist file, you have to use the regular document storage area… copy this plist file over to document storage, and save/write your file there.