user2364214
user2364214

Reputation: 313

Internal memory in iOS simulator

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

Answers (1)

Rob
Rob

Reputation: 437802

You need to use persistent storage if you want to save data between sessions. Options include:

  1. 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.

  2. 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.

  3. You can use NSUserDefaults. This is really better suited for app settings and defaults, but theoretically could be used for saving data, too.

  4. 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.

  5. 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

Related Questions