"When one teaches, two learn..."
A blog about iPhone development.
(Trace amounts of Java and JavaScript might be found.)
Friday, March 12, 2010
Revisited: Storing and retrieving information using plists
When I wrote the original post I hadn't really resolved how to store information on a real device in the proper way. For example, if you have a file in your application bundle which you want to update, you should start by copying the file from the bundle to the Documents-directory.
To resolve the issues from the first post and show the "proper way" of handling files that are updated by the application I simply created a new Window-based Application and edited the "applicationDidFinishLaunching" method to look like this:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Override point for customization after application launch
[window makeKeyAndVisible];
// get the path to the "Documents" directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
// get the path to our plist ("Documents/foo.plist")
NSString *plistPath = [documentsDirectory stringByAppendingPathComponent:@"foo.plist"];
// read or create plist
NSMutableDictionary *dict;
// check if our plist already exists in the Documents directory...
NSFileManager *fileManager = [NSFileManager defaultManager];
if ( [fileManager fileExistsAtPath:plistPath] ) {
// ...if it does, read it
NSLog(@"dict existed, reading %@", plistPath);
dict = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];
} else {
// ...if it doesn't, create it
NSLog(@"dict didn't exist, creating...");
dict = [NSMutableDictionary dictionaryWithCapacity:1];
// Fill the dictionary with default values, either by copying
// a default plist from our bundle to the Documents directory
// or simply creating a new dictionary and writing it to th
// Documents directory. Here we choose to create a new
// dictionary rather than providing a default plist in the bundle.
// create a NSNumber object containing the
// integer value 1 and add it as 'key1' to the dictionary.
NSNumber *number = [NSNumber numberWithInt:1];
[dict setObject:number forKey:@"key1"];
// write dictionary to Documents directory...
NSLog(@"writing to %@...", plistPath);
[dict writeToFile:plistPath atomically:YES];
}
// dump the contents of the dictionary to the console
NSLog(@"dumping...");
for (id key in dict) {
NSLog(@"key=%@, value=%@", key, [dict objectForKey:key]);
}
// check if key2 is present
NSString *value2 = (NSString *)[dict valueForKey:@"key2"];
if ( value2 == nil ) {
NSLog(@"key2 didn't exist, adding...");
[dict setObject:@"default-2" forKey:@"key2"];
}
// dump the contents of the dictionary to the console
NSLog(@"dumping...");
for (id key in dict) {
NSLog(@"key=%@, value=%@", key, [dict objectForKey:key]);
}
}
The code should be rather self-explanatory thanks to the inline comments, but I really recommend you to read my first post on this subject to really understand what is going on.
A thing worth commenting on is the really strange path names logged to the console. These pathnames can look like this:
2010-03-12 09:09:15.557 Plist2[10780:20b] writing to /Users/henrik/Library/Application Support/iPhone Simulator/User/Applications/E10D7186-1219-4C52-804A-FE217F760967/Documents/foo.plist...
This path is a path in the Mac OS filesystem used by the iPhone simulator when testing an application. The strange part of it is the long string of hexadecimal numbers starting with E10D7. This is the so called GUID which is unique for each installed application. A lot has been written about this in other sources on the Internet, so I won't go further into that:
One annoying thing about it and that is that it changes each time you recompile your application. That means, that each time you build and test your application the files created by the application will be somewhere else in the Mac OS filesystem. This makes it hard to inspect the files written by your application. Fortunately, modern versions of XCode copy the contents from the "old path" to the "new path" so even though the GUID changes, the files created by the application the last time are available the next time you build and test it.
If you couple this annoying thing with the fact that nothing is logged to the console if you exit your application by pressing the home-button in the simulator and then relaunch the application by pressing its icon in the simulator. This means that if you want to see what is logged to the console you are forced to relaunch the application from XCode, which will create a new GUID and thus a new path in the Mac OS filesystem.
However, since XCode nowadays copies the contents from the old to the new path it is possible to test how the application responds to "external changes" such as removing a file stored by the application, etc. This can be very convenient to perform effecient testing of an application.
For the application in this post you can test how it behaves if "foo.plist" is present or not by first running the application from XCode (CMD-Return), observing which path is logged to console, removing that file and then re-running the application from XCode (CMD-Return). This will give you something like this on the console:
[Session started at 2010-03-12 10:18:24 +0100.]
2010-03-12 10:18:27.456 Plist2[10888:20b] dict existed, reading /Users/henrik/Library/Application Support/iPhone Simulator/User/Applications/DB3B1A75-BF08-461E-919A-DBFFCC994036/Documents/foo.plist
2010-03-12 10:18:27.464 Plist2[10888:20b] dumping...
2010-03-12 10:18:27.468 Plist2[10888:20b] key=key1, value=1
2010-03-12 10:18:27.469 Plist2[10888:20b] key2 didn't exist, adding...
2010-03-12 10:18:27.471 Plist2[10888:20b] dumping...
2010-03-12 10:18:27.475 Plist2[10888:20b] key=key1, value=1
2010-03-12 10:18:27.477 Plist2[10888:20b] key=key2, value=default-2
[Session started at 2010-03-12 10:19:38 +0100.]
2010-03-12 10:19:40.159 Plist2[10897:20b] dict didn't exist, creating...
2010-03-12 10:19:40.180 Plist2[10897:20b] writing to /Users/henrik/Library/Application Support/iPhone Simulator/User/Applications/5B1E8FF3-45D6-443B-BB0B-2073EAE22520/Documents/foo.plist...
2010-03-12 10:19:40.191 Plist2[10897:20b] dumping...
2010-03-12 10:19:40.198 Plist2[10897:20b] key=key1, value=1
2010-03-12 10:19:40.199 Plist2[10897:20b] key2 didn't exist, adding...
2010-03-12 10:19:40.199 Plist2[10897:20b] dumping...
2010-03-12 10:19:40.200 Plist2[10897:20b] key=key1, value=1
2010-03-12 10:19:40.202 Plist2[10897:20b] key=key2, value=default-2
Tuesday, September 1, 2009
iphone tutorial: Memory management - autorelease and NIB files
In this tutorial we're going to dive a bit deeper into Cocoa memory management, specifically looking at a feature called autorelease. If you haven't read the first part of the memory management tutorial, it's highly recommended that you do.
Cocoa memory ownership rules
In the first part of this tutorial we introduced the concept of memory ownership. To help deciding who actually owns a memory block, the Cocoa designers have developed a set of rules that all Cocoa classes and applications should follow. This set of rules can be found in the "Memory Management Programming Guide for Cocoa":
---
To make sure it is clear when you own an object and when you do not, and what responsibilities you have as an owner, Cocoa sets the following policy:
You own any object you create.
You “create” an object using a method whose name begins with “alloc” or “new” or contains “copy” (for example, alloc, newObject, or mutableCopy).
If you own an object, you are responsible for relinquishing ownership when you have finished with it. You relinquish ownership of an object by sending it a release message or an autorelease message (autorelease is discussed in more detail in “Delayed Release”). In Cocoa terminology, relinquishing ownership of an object is typically referred to as “releasing” an object.
If you do not own an object, you must not release it.
---
If you think these rules are too complicated to remember, try to at least remember the naming scheme; methods beginning with "alloc" or "new" or contains "copy" creates an object (and returns a reference to that object) that you are responsible for freeing. That is, these methods, transfer the ownership to you, the caller of the moethod.
A new concept is also introduced; autorelease or "delayed release". If you call 'autorelease' on an object reference, that object won't be released immediately, but "later". Before we explain what later means, it might be good to understand why there is such a feature as a "delayed release".
Autorelease
Why would you want to release an object later instead of now or not at all? One example is when you have a method that creates objects for someone else than itself.
(MyClass.m)
+(MyClass *)createObject {
MyClass *reference = [[MyClass alloc] init];
return reference;
}
The method 'createObject' in the class MyClass allocates and initialises a MyClass object and then returns the reference to the newly created object. An application calling this method
(MyApplication.m)
MyClass *myClass = [MyClass createObject];
would not be responsible for releasing the object since the 'createObject' method name doesn't contain any of the "magic words" listed in the rules above. The 'createObject' method on the other hand calls 'alloc' which is a magic word and is therefore responsible for calling release.
It's impossible to call 'release' on the created object after the return statement and 'release' is called before the return statement, the object will be deallocated and the method will return a reference to a deallocated object which will lead to chaos. So when should 'release' be called? Well, "later"...
Here "later" means "after the return statement" and late enough so that the caller of the method gets a chance to store the returned release in a variable (or somewhere else) and call 'retain' on it.
This is where the autorelease mechanism steps in and provides a solution. If the method creating the object calls 'autorelease' on the reference instead of 'release' evertyhing will work; the method has fulfilled its responsibility to release the object it created and the caller of the method gets a chance to call retain on the returned object.
(MyClass.m)
+(MyClass *)createObject {
MyClass *reference = [[MyClass alloc] init];
[reference autorelease]; // call autorelease on the newly create object
return reference;
}
(MyApplication.m)
MyClass *myClass = [MyClass createObject];
[myClass retain]; // call retain on the returned object to protect it from being deallocated
The autorelease method can be called anywhere the release method should have been called but just as with release it's important to call it the right number of times; for each time 'autorelease' is called 'release' will be called "later".
NIB files
We mentioned above that a method that creates an object for someone else is a typical user of the autorelease functionality. Another user of autorelease is a NIB file, or actually the method that load it. A NIB file contains a graph of objects where most objects are connected to each other using outlets. When the loader method rebuilds this object graph in memory, using the NIB file as a blueprint, it creates all objects with a reference count of 1 and then autorelease them.
When the connections between the objects are re-established, the loader method calls setter methods of the objects, which should retain the object reference if they want to prevent the newly loaded object from being deallocated. Ownership is transferred to these objects, meaning that the objects also are responsible for releasing the references in their 'dealloc' method. Setter methods for outlets are normally synthesized using the @property/@synthesize mechanism of Objective C.
@property (nonatomic, retain) IBOutlet someClass *someOutlet;
The 'retain' keyword of the property declaration will ensure that the corresponding setter method created by the @synthesize keyword will retain the reference passed to it.
However, there are also top-level objects in the file that have no natural owner. If you want to keep those objects around, which you normally do, you have to manually retain them (and then later manually releasing them). But how do you get hold of the references to them if they have no "owners"? Well, they do actually get a temporary owner during the loading process; references to all the top-level objects are stored in an array, returned by the 'loadNibNamed:owner:options' method.
As a simple alternative to iterating through the array and retain all the objects in it individually you could retain the array itself instead (and then later release it). This works since it prevents the array from being (auto) released and deallocated - if it was it would release all the references stored in it (decreasing the reference count to zero) and thus force all the objects in the array to be deallocated.
NSArray *nibArray = [[NSBundle mainBundle] loadNibNamed:@"Nib1" owner:self options:nil];
if ( nibArray == nil ) {
NSLog(@"loadNibNamed failed");
return;
}
[nibArray retain];
NIB files with "unconnected" top-level objects aren't that common since it would be useless to define a lot of objects in a NIB that no one uses. Much more common is that the top-level objects are connected to outlets in the "File's owner" object. When that is the case you don't have to worry about implicitly retaining the objects since they will be retained by the setter methods in the "File's owner".
Friday, May 29, 2009
iPhone tutorial: Creating table cells in Interface Builder




Tuesday, May 26, 2009
iPhone tutorial: UITableView from the ground up, part 3
Sunday, May 24, 2009
iPhone tutorial: UITableView from the ground up, part 2
Saturday, May 23, 2009
iPhone tutorial: UITableView from the ground up, part 1
Wednesday, May 13, 2009
iPhone tutorial: Storing and retrieving information using plists

Some applications need to store information in order to be really valuable for the user. Others do it just for fun; for example, storing the highscores in a game. Yet others do it to offer a multi-platform experience, by making it possible to access the same information from several different platforms (web, desktop, mobile), where the iPhone might be one of them. In the last case, the information should probably be stored on a server on the Internet to make it really useful.