Saving, Loading, and Application States

Saving, Loading, and Application States

Archiving

Archiving类似JAVA的序列化,可以将对象保存到硬盘上,也可加载硬盘上的数据来重新创建对象。

如果一个类要支持归档,需要实现NSCoding protocol,并实现encodeWithCoder: initWithCoder:方法.

在BKItem.h中声明确认NSCoding协议。

@interface BKItem : NSObject 

在BKItem.m中实现这两个方法:

// archived
- (void)encodeWithCoder:(NSCoder *)aCoder{
    [aCoder encodeObject:self.itemName forKey:@"itemName"];
    [aCoder encodeObject:self.serialNumber forKey:@"serialNumber"];
    [aCoder encodeObject:self.dateCreated forKey:@"dateCreated"];
    [aCoder encodeObject:self.itemKey forKey:@"itemKey"];
    
    [aCoder encodeInt:self.valueInDollars forKey:@"valueInDollars"];
}

// unarchived
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
    self = [super init];
    
    if (self) {
        _itemName = [aDecoder decodeObjectForKey:@"itemName"];
        _serialNumber = [aDecoder decodeObjectForKey:@"serialNumber"];
        _dateCreated = [aDecoder decodeObjectForKey:@"dateCreated"];
        _itemKey = [aDecoder decodeObjectForKey:@"itemKey"];
        
        _valueInDollars = [aDecoder decodeIntForKey:@"valueInDollars"];
    }
    return self;
}

Application Sandbox

每个应用都有自己的application sandbox,其他应用无法访问你的应用的sandbox。


Application sandbox

application sandbox由以下目录组成:

directory description
application bundle 只读目录,包含应用的可执行文件以及资源文件,如图片,NIB等
Documents/ 该目录下的内容可以同步到iTunes或iCloud
Library/Caches/ 缓存目录,缓存后台服务器的资源数据,即使丢失,也可以从后台再次获取到
Library/Preferences/ application preference,会同步到iTunes或iCloud.NSUserDefaults处理此目录数据
tmp/ 临时目录,NSTemporaryDirectory指向此目录

将BKItem保存到Documents目录,在BKItemStore.m中添加如下方法:

// 返回application sandbox的Documents目录
- (NSString *)itemArchivePath{
    NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    
    NSString *documentDirectory = [documentDirectories firstObject];
    
    return [documentDirectory stringByAppendingPathComponent:@"items.archive"];
}

NSKeyedArchiver and NSKeyedUnarchiver

利用NSKeyedArchiver将数据写到指定路径。

在BKItemStore.h中声明方法来保存数据到文件系统:

- (BOOL)saveChanges;

在BKItemStore.m中实现此方法:

- (BOOL)saveChanges{
    NSString *path = [self itemArchivePath];
    
    // 1. 创建NSKeyedArchiver实例对象;
    // 2. 调用privateItems对象(NSMutableArray)的encodeWithCoder:方法,形参是NSKeyedArchiver实例;
    // 3. 数组向其内部的每个BKItem对象,发送encodeWithCoder:消息,形参是同样的NSKeyedArchiver实例;
    // 4. NSKeyedArchiver写数据到指定路径上
    return [NSKeyedArchiver archiveRootObject:self.privateItems toFile:path];
}

什么时候来调用此方法呢?
当用户点击home键,会发送applicationDidEnterBackground:消息给App Delegate,在该方法中保存数据到文件系统。

#import "BKItemStore.h"

@implementation BKAppDelegate
- (void)applicationDidEnterBackground:(UIApplication *)application
{
    BOOL success = [[BKItemStore sharedStore] saveChanges];
    if (success) {
        NSLog(@"Saved all of the BKItems.");
    } else {
        NSLog(@"Could not save any of the BKItems.");
    }
}

在BKItemStore.m的初始化方法中,从归档文件中反序列化对象,调用NSKeyedUnarchiver的unarchiveObjectWithFile:方法。

- (instancetype)initPrivate{
    self = [super init];
    if(self){
        
        // Unarchive data
        NSString *path = [self itemArchivePath];
        _privateItems = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
        
        if (!_privateItems) {
            _privateItems = [[NSMutableArray alloc] init];
        }
    }
    return self;
}

现在可以动态的创建BKItem,保存到文件系统,加载他们。修改BKItemStore.m的createItem方法,不再生成随机值:

- (BKItem *)createItem{
    //BKItem *item = [BKItem randomItem];
    BKItem *item = [[BKItem alloc] init];
    
    [self.privateItems addObject:item];
    
    return item;
}

Application States and Transitions

下图是应用的各种状态,以及状态切换时执行的方法。


Application states:

State Visible Receives Events Executes Code
Not Running No No No
Active Yes Yes Yes
Inactive Mostly No Yes
Background No No Yes
Suspended No No No

Writing to the Filesystem with NSData

利用NSData将图片写到文件系统。
在BKImageStore.m中添加如下方法,根据图片KEY返回图片路径:

// 根据指定的key, 返回图片的保存路径
- (NSString *)imagePathForKey:(NSString *)key{
    NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentDirectory = [documentDirectories firstObject];
    
    return [documentDirectory stringByAppendingPathComponent:key];
}

保存图片到文件系统:

- (void)setImage:(UIImage *)image forKey:(NSString *)key{
    //[self.dictionary setObject:image forKey:key];
    self.dictionary[key] = image;

    // 保存图片到文件系统
    NSString *imagePath = [self imagePathForKey:key];
    NSData *data = UIImageJPEGRepresentation(image, 0.5);
    [data writeToFile:imagePath atomically:YES];
}

删除图片:

- (void)deleteImageForKey:(NSString *)key{
    if (!key) {
        return;
    }
    [self.dictionary removeObjectForKey:key];
    
    // 删除文件系统上的图片
    NSString *imagePath = [self imagePathForKey:key];
    [[NSFileManager defaultManager] removeItemAtPath:imagePath error:nil];
}

从文件系统上读取图片:

- (UIImage *)imageForKey:(NSString *)key{
    //return [self.dictionary objectForKey:key];
    //return self.dictionary[key];
    
    UIImage *result = self.dictionary[key];
    if (!result) {
        NSString *imagePath = [self imagePathForKey:key];
        
        // Create UIImage object from file
        result = [UIImage imageWithContentsOfFile:imagePath];
        
        if (result) {
            self.dictionary[key] = result;
        } else {
            NSLog(@"Error: unable to find %@", [self imagePathForKey:key]);
        }
    }
    return result;
}

NSNotificationCenter and Low-Memory Warnings

每个应用都有一个NSNotificationCenter实例。
当一个对象发送notification,notification center会转会此notification到其observer。

比如当内存过低,notification center会收到UIApplicationDidReceiveMemoryWarningNotification,可以在notification center中添加此notification的observer。

BKImageStore.m中,将其注册为UIApplicationDidReceiveMemoryWarningNotification的observer,当内存过低时,清空图片所占内存。

- (instancetype)initPrivate{
    self = [super init];
    if (self) {
        _dictionary = [[NSMutableDictionary alloc] init];
        
        // 将BKImageStore注册了内存过低notification的observer
        NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
        [nc addObserver:self selector:@selector(clearCache:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
    }
    return self;
}

- (void)clearCache:(NSNotification *)note{
    NSLog(@"flushing %d images out of the cache", [self.dictionary count]);
    [self.dictionary removeAllObjects];
}

NSNotification

除了delegation和target-action pairs,notification是另外一种回调。不过delegation和target-action paris会直接发送消息到delegate和target,notification使用middl-man: NSNotificationCenter

每个NSNotification对象都有name(notification center通过名字查找observer),object(此对象负责发送notification),可选的userInfo dictionary(包含一些poster想让observer知道的信息)。

addObserver:selector:name:object:方法的最后一个参数如果是nil,表示任何对象发送notification,observer都会收到。如果指定了某个对象,则只有当此对象发送notification时,observer才会收到notification。

同一个notification,可以有多个对象是其observer,所以当有多个对象需要响应同一个event时,就需要使用notification。

NSNotificationCenter和push/local notification没有关系。

Reading and Writing to the Filesystem

对于binary data使用NSData,文本使用NSString的两个实例方法writeToFile:atomically:encoding:error: 和initWithContentsOfFile:。除了NSString有这两个方法,NSDictionary 和 NSArray也有,不过他们写到文件系统后生成的是XML文件,而且NSDictionary 和 NSArray中的对象必须是可序列化的(property list serializable)对象:NSString, NSNumber, NSDate, NSData, NSArray, and NSDictionary.。

    NSError *err;
    
    NSString *someString = @"Text Data";
    BOOL success = [someString writeToFile:@"/some/path/file" atomically:YES encoding:NSUTF8StringEncoding error:&err];
    
    if (success) {
        NSLog(@"Error writing file: %@", [err localizedDescription]);
    }
    
    NSString *myEssay = [[NSString alloc] initWithContentsOfFile:@"/some/path/file" encoding:NSUTF8StringEncoding error:&err];
    
    if (!myEssay) {
        NSLog(@"Error reading file %@", [err localizedDescription]);
    }

注意上面代码中的err对象,开始并没有创建error对象,只是声明了一个NSError类型的变量,只有当错误发生时才需要真正创建错误对象。&err表示变量的内存地址。

UIAlertView

如果要向用户显示错误提示,可以使用UIAlertView对象。

    UIAlertView *a = [[UIAlertView alloc] initWithTitle:@"Error" message:[err localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
    [a show];

The Application Bundle

在Build Phases中查看application bundle。


在app在模拟器上运行后,查看~/Library/Application Support/iPhone Simulator/ (version number)/Applications目录,可以看到application sandbox,点击 查看包内容。


可以读取application bundle中的文件,但是不能修改和添加文件:

// Get a pointer to the application bundle
NSBundle *applicationBundle = [NSBundle mainBundle];
// Ask for the path to a resource named myImage.png in the bundle
NSString *path = [applicationBundle pathForResource:@"myImage" ofType:@"png"];

本文是对《iOS Programming The Big Nerd Ranch Guide 4th Edition》第十八章的总结。


最近又被派到到JAVAEE项目了,而且开始加班,看书的时间越来越少了。

你可能感兴趣的:(Saving, Loading, and Application States)