Reputation: 313
I just finished the second tutorial for iOS development. When I add a new sighting, it works fine and turns up in the list of sightings. However, when I close the iOS simulator and reopen it, only the first entry is there (it is added manually). Should there be internal memory in the iOS simulator even after it is closed and reopened? There seems to be some memory as if I add my contact info to the contacts, it is still there when I reopen it. If so, how do I make sure that my array in the DataController file is similarly stored on the simulator/phone so it doesn't clear itself every time I reopen the simulator?
Thanks
Upvotes: 1
Views: 551
Reputation: 437802
You need to use persistent storage if you want to save data between sessions. Options include:
You can use plist files. For example if you have NSArray *array
, you can save that to a plist using writeToFile
:
[array writeToFile:filename atomically:YES];
You can then read this array with:
NSArray *array = [NSArray arrayWithContentsOfFile:filename];
This technique only works with standard NSString
, NSNumber
, etc., objects, not custom objects like BirdSighting
, though.
For custom objects like BirdSighting
, you could use NSKeyedArchiver
and NSKeyedUnarchiver
. By the way, these are not only generally useful classes for saving data for small data sets like this, but given that it features prominently in the new iOS 6 state preservation features, it's worth familiarizing yourself with this pattern.
You can use NSUserDefaults
. This is really better suited for app settings and defaults, but theoretically could be used for saving data, too.
You can use CoreData. This is the preferred iOS technology for object persistence. It's a powerful and well engineered framework (though a tad complicated) and well suited if you're dealing with more significant amounts of data.
You can use SQLite, too. See this Ray Wenderlich article on using SQLite. And once you start using SQLite, you can consider using FMDB to simplify your coding effort.
If you wanted, for example, to use the second approach, NSKeyedArchiver
and NSKeyedUnarchiver
, the first thing is that you might want to do is make BirdSighting
conform to NSCoding
, by altering the @interface
declaration in BirdSighting.h
to say:
@interface BirdSighting : NSObject <NSCoding>
Second, you have to write the two NSCoding
methods, initWithCoder
and encodeWithCoder
, in BirdSighting.m
that define what properties can to be loaded/saved for this object:
- (NSArray *)keysForEncoding;
{
return @[@"name", @"location", @"date"];
}
- (id) initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
for (NSString *key in [self keysForEncoding])
{
[self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
for (NSString *key in self.keysForEncoding)
{
[aCoder encodeObject:[self valueForKey:key] forKey:key];
}
}
Your BirdSighting
can now be loaded and saved with NSKeyedUnarchiver
and NSKeyedArchiver
, respectively.
So, focusing on the loading of the sightings, you have to (a) tell BirdSightingDataController.m
what file to look for; and (b) instruct it to read that file during initialization:
- (NSString *)filename
{
NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
return [docsPath stringByAppendingPathComponent:@"BirdSightings"];
}
- (void)initializeDefaultDataList
{
NSString *filename = [self filename];
self.masterBirdSightingList = nil;
if ([[NSFileManager defaultManager] fileExistsAtPath:filename])
{
self.masterBirdSightingList = [NSKeyedUnarchiver unarchiveObjectWithFile:filename];
}
if (!self.masterBirdSightingList)
{
NSMutableArray *sightingList = [[NSMutableArray alloc] init];
self.masterBirdSightingList = sightingList;
BirdSighting *sighting;
NSDate *today = [NSDate date];
sighting = [[BirdSighting alloc] initWithName:@"Pigeon" location:@"Everywhere" date:today];
[self addBirdSightingWithSighting:sighting];
}
}
The BirdSightingDataController.m
can also define a method to save the data:
- (BOOL)save
{
return [NSKeyedArchiver archiveRootObject:self.masterBirdSightingList toFile:[self filename]];
}
You can now, for example, call this save
method whenever you add a sighting, e.g.:
- (void)addBirdSightingWithSighting:(BirdSighting *)sighting
{
[self.masterBirdSightingList addObject:sighting];
[self save];
}
Personally, rather than saving it every time a user does any change in the app, I might rather just have my app delegate save it when the app goes into background or terminates (but that requires further changes, so I won't go into that now).
But hopefully this code illustrates how you can use NSKeyArchiver
and NSKeyUnarchiver
to save and load data. And clearly, for more complicated scenarios, I would generally encourage you to consider Core Data. But for small data sets, like this, this archiver pattern can be useful (and as I said, is worth being familiar with because the basic technology is also used in iOS 6 app state restoration).
Upvotes: 2