I have researched long and hard about this topic but cannot find a solution. I am very new to iphone app development in xcode so please make the answer comprehensible lol. I followed apple's "Your Second iOS App" tutorial and have the master detail app working perfectly but now I want to save the data the user enters in so that it will appear after resetting the app. The code is the same as what is on apple tutorial here
Thanks in advance!
There are two main decisions you need to make: how you are going to store the data, and when are you going to store/retrieve the data. Let's begin with how to store the data.
How to Store the Data
There are three main approaches you can take: save to a file, save to a SQLite database (either with or without a wrapper such as FMDB), use CoreData. Eventually you will want to master SQLite and CoreData, but for now I recommend taking the first approach.
If you are going to save to/read from a file, you need to decide on the format. There are a number of different options including XML, JSON, your own custom format, etc. In fact, for a "real" project, and depending on the project goals, a format such as JSON is likely to be a good choice. However, I am going to discuss using a serialized archive since a lot of the machinery is already in place.
To store your data in an archive, you will use the NSKeyedArchver to write the data to a file. Later, you will use NSKeyedUnarchiver to retrieve the information from the file. These classes encode/decode your data to/from a byte stream which can then be sent over the wire, written to a file, etc.
Your main data structure is the NSMutableArray named masterBirdSightingList, so the archiver needs to encode the array. NSArray and its mutable counterpart already know how to encode/decode themselves: they just encode/decode each of their elements along with some bookkeeping information. So there is a missing piece to the puzzle. You need to specify how to encode/decode instances of the BirdSighting class.
To specify this, you modify the class so that it implements the NSCoding protocol. In BirdSighting.h, change the @interface declaration to
@interface BirdSighting : NSObject <NSCoding>
Implementing NSCoding is straight-forward. I mentioned above that the various array classes know how to encode/decode themselves, i.e. they already implement the NSCoding protocol. Well to encode/decode a BirdSighting, we just need to encode/decode each of the data members of the class. Looking at the source code, I see they are two NSStrings and an an NSDate. But each of these classes also already implements NSCoding. So in order to encode/decode a BirdSighting instance, we just need to tell each of our instance variables to encode/decode themselves. We do all this work in the methods initWithCoder: and encodeWithCoder: which you add to the BirdSighting class.
Note: one of the details I'm glossing over is the difference between an archiver and a keyed archiver. Usually, you will want to go with a keyed archiver, hence the three macro definitions. And to be honest, I would create NAME_KEY etc., as static NSString constants instead of macros.
#define NAME_KEY @"name"
#define LOCATION_KEY @"location"
#define DATE_KEY @"date"
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
_name = [aDecoder decodeObjectForKey:NAME_KEY];
_location = [aDecoder decodeObjectForKey:LOCATION_KEY];
_date = [aDecoder decodeObjectForKey:DATE_KEY];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.name forKey:NAME_KEY];
[aCoder encodeObject:self.location forKey:LOCATION_KEY];
[aCoder encodeObject:self.date forKey:DATE_KEY];
}
One more note, since BirdSighting inherits directly from NSObject (which does not implement NSCoding), I use self = [super init]
. If the parent class did implement NSCoding, you would want to do self = [super initWithCoder:aCoder]
.
With the above in place, saving to/reading from a file is easy. Somewhere in BirdSightingDataController, probably in initializeDefaultDataList, we insert the following:
masterBirdSightingList = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
where path is an NSString containing the directory path to the archive file and the usual caveats about needing error checking and handling in production code (e.g. what if the file doesn't exist).
Saving the data is easy as well. We can add a method such as the following to the BirdSightingDataController object:
- (BOOL)archiveToPath:(NSString *)path
{
BOOL success = [NSKeyedArchiver archiveRootObject:self.masterBirdSightingList];
return success;
}
When to Store the Data
Now that we have code in place to save and restore the data, we need to decide when we are going to perform these operations. Here I am going to be a little more vague because other issues, such as overall app structure, come into play. However, the app delegate is a likely candidate for managing archiving and unarchiving of the list because of the methods relating to running in the background such as applicationWillResignActive: and applicationWillEnterForeground:.
Deleting the in-memory copy of the sightings list before going into the background is a good thing to do. This suggests putting the code to create the archive in the app delegate. Conversely, you should consider lazy loading the list, i.e. don't retrieve it until you are ready to display it, and this suggests putting the code to retrieve the list in the init method for BirdSightingDataController.
Now your challenge is to do all this without over-coupling and over-complicating your app. But I'll leave that discussion to another Q&A.