SlideShare una empresa de Scribd logo
1 de 65
Descargar para leer sin conexión
保存,加载,及应⽤用程序状态




范圣刚,princetoad@gmail.com www.tfan.org
• 在⼀一个 iOS application 中保存和加载数据有很多
 ⽅方法。

• 这⼀一个主题我们来看⼀一下⼀一些最常⻅见的机制,以
 及在 iOS 上对⽂文件系统进⾏行读写需要理解的概念
Archiving
• 任何 iOS 应⽤用实际都在做⼀一件事情:提供给⽤用户
⼀一个界⾯面让他们来操作数据。

• 在应⽤用中的每⼀一个对象在这个过程中都扮演下⾯面
的⼀一个⾓角⾊色。
• Model 对象,负责持有⽤用户操作的数据
• View 对象,只是体现这些数据
• Controllers,负责应⽤用运⾏行到底是怎么回事
• 因此当我们讨论保存和加载数据时,我们⼏几乎总
是在谈论保存和加载 Model 对象
• 在 Homepwner 中,⽤用户操作的模型对象是
BNRItem 的实例。

• 如果在应⽤用退出后再启动时 BNRItem 的实例能够
持久存在,Homepwner 将真正成为⼀一个有⽤用的应
⽤用

• 在这⼀一章,我们将使⽤用 archiving 来保存和加载
BNRItem
• Archiving 是在 iOS 上持久化模型数据的⼀一种最常
 ⻅见的⽅方法。

• Archiving ⼀一个对象会记录它所有的实例变量并且
 把他们保存到⽂文件系统。

• Unarchiving ⼀一个对象就是从⽂文件系统加载数据,
 并且再从这个记录⽣生成对象。
NSCoding
 • 需要把它的实例进⾏行 archive 和 unarchive 的类必
    须符合 NSCoding protocol。
 • 并且实现它的两个必须的⽅方法,
    encodeWithCoder: 和 initWithCoder:
@protocol NSCoding

- (void)encodeWithCoder:(NSCoder *)aCoder;
- (id)initWithCoder:(NSCoder *)aDecoder;
encode
 • 在 BNRItem.h 中增加 NSCoding protocol 的声明
@interface BNRItem : NSObject <NSCoding>



• 然后是实现两个必须的⽅方法,⾸首先是
  encodeWithCoder:
• 当 BNRItem 被发送 encodeWithCoder: 消息时,它
 将把它的所有的实例变量编码到作为⼀一个参数传
 ⼊入的 NSCoder 对象中

• 我们可以把 NSCoder 对象想成⼀一个数据容器,负
 责组织这些数据

• NSCoder 以key-value pair 的⽅方式组织数据
encodeWithCoder:
    • 在 BNRItem.m 中实现 encodeWithCoder: 以添加实
     例变量到这个容器
- (void)encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeObject:itemName forKey:@"itemName"];
    [aCoder encodeObject:serialNumber forKey:@"serialNumber"];
    [aCoder encodeObject:dateCreated forKey:@"dateCreated"];
    [aCoder encodeObject:imageKey forKey:@"imageKey"];

     [aCoder encodeInt:valueInDollars forKey:@"valueInDollars"];
}
encodeXXX:forKey
• 指向对象的指针使⽤用 encodeObject:forKey: 编码
• valueInDollars 使⽤用 encodeInt:forKey: 编码,还有
 很可以 encode 的类型
• 不管被编码的值是什么类型,总有⼀一个 key 存在
• key 是⼀一个⽤用来标识哪个实例变量在被编码的字符
串。按照惯例,这个 key 就是被编码的实例变量
的名字
编码对象的递归过程
• ⼀一个对象被编码时,被发送 encodeWithCoder: 。
 当⼀一个对象被发送 encodeWithCoder:,它⽤用同样
 的⽅方式编码它的实例变量 - 也是给它们发送
 encodeWithCoder:
• 这样编码⼀一个对象就是⼀一个对象编码其他对象的
 递归过程
编码⼀一个对象




• 想要被编码,这些对象必须也符合
 NSCoding

• NSDate, NSString 都是 NSCoding 兼容的
initWithCoder:
• encoding 时使⽤用 key 的⺫⽬目的是为了之后在
 BNRItem 被从⽂文件系统加载时⽤用来获取已编码的
 值
• 被从⼀一个 archive 中加载的对象被发送的是
 initWithCoder: 消息。
• 这个⽅方法攫取所有在 encodeWithCoder: 中被编码
 的对象,并且把它们分配给合适的实例变量。
• 在 BNRItem.m 中实现这个⽅方法
decodeXXXForKey:
- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];
    if (self) {
        [self setItemName:[aDecoder decodeObjectForKey:@"itemName"]];
        [self setSerialNumber:[aDecoder
decodeObjectForKey:@"serialNumber"]];
        [self setImageKey:[aDecoder decodeObjectForKey:@"imageKey"]];
        [self setValueInDollars:[aDecoder
decodeIntForKey:@"valueInDollars"]];
        dateCreated = [aDecoder decodeObjectForKey:@"dateCreated"];
    }

     return self;
}


    • 这个⽅方法也有⼀一个 NSCoder 参数,它⾥里⾯面的数据
     是供正在被初始化的 BNRItem 使⽤用的
    • 使⽤用 decodeObjectForKey: 取回对象(object),
     decodeIntForKey: 取回 valueInDollar
• 经过前⾯面的改造之后,BNRItem 现在是 NSCoding
 兼容的了,⽽而且也可以使⽤用 archiving 从⽂文件系统
 进⾏行保存和加载。

• 现在还有两个问题要考虑:
 • 我们需要⼀一种⽅方法来开启保存和加载操作?(现在只
  是可以 archiving 和 unarchiving)

 • 另外,我们需要在⽂文件系统找⼀一个地⽅方来存储我们要
  保存的 BNRItem
Application Sandbox
• 每⼀一个 iOS 应⽤用都有它⾃自⼰己的 applicaton
 sandbox(应⽤用沙箱)。

• ⼀一个 application sandbox 是在⽂文件系统上的⼀一个
 和⽂文件系统其余部分隔离的⺫⽬目录。

• 应⽤用必须位于这个 sandbox 内,并且没有其他的
 应⽤用可以访问你的 sandbox。
Application Sandbox
application sandbox
• Library/Preferences/
• tmp/
• Documents/
• Library/Caches
Library/Preferences/
• 所有的⾸首选项都存在这⾥里;“Settings”应⽤用也在这
 ⾥里查找应⽤用程序⾸首选项
• Library/Preferences 被 NSUserDefaults 类⾃自动化的
 进⾏行处理
• 当设备和 iTunes 或 iCloud 同步时会被备份
tmp/
• ⽤用于写⼊入应⽤用运⾏行时临时⽤用到的数据,⽤用完以后
 应该从这个⺫⽬目录删除
• 不备份
• 使⽤用函数 NSTemporaryDirectory 拿到 application
 sandbox 中 tmp ⺫⽬目录的路径
Documents
• 需要持久化存储的数据可以写到这⾥里
• 备份
Library/Caches
• 应⽤用程序⽣生成的,还希望能够持久存在
• 不会被备份
• 不被备份的⼀一个主要的原因就是这些⽂文件可能会
 很⼤大,会延⻓长同步设备的时间
构造⼀一个⽂文件路径
• 来⾃自 Homepwner 的 BNRItem 将被保存到
 Documents ⺫⽬目录中⼀一个单独的⽂文件中
• BNRItemStore 将处理⽂文件写⼊入和读取的⼯工作
• ⾸首先,我们需要来构建这个⽂文件的路径
• 在 BNRItemStore 中声明和实现⼀一个新的⽅方法
- (NSString *)itemArchivePath
{
    NSArray *documentDirectories =
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,
YES);
    NSString *documentDirectory = [documentDirectories objectAtIndex:0];

    return [documentDirectory
stringByAppendingPathComponent:@"items.archive"];
}


 • NSSearchPathForDirectoriesInDomains 函数搜索
    ⽂文件系统来查找符合给定参数标准的路径

 • 在 iOS 上,最后两个参数总是⼀一样的
 • 第⼀一个参数是指定了在 sandbox 中⺫⽬目录的常量。
    ⽐比如搜索 NSCachesDirectory 将会返回在应⽤用的
    sandbox 中的 Caches ⺫⽬目录

 • 返回值是⼀一个字符串数组,在 iOS 上只有⼀一个,
    所以只取第⼀一个就可以了
NSKeyedArchiver 和
NSKeyedUnarchiver
• 现在我们已经有了⼀一个⽂文件系统的路径,同时模
    型对象(BNRItem)也可以被保存到⽂文件系统了

 • 我们可以使⽤用 NSKeyedArchiver 在应⽤用 “退出” 时
    保存 BNRItems

 • 在 BNRItemStore.h 中声明⼀一个新的⽅方法:
- (BOOL)saveChanges;
archiveRootObject:
- (BOOL)saveChanges
{
    // 返回成功或失败
    NSString *path = [self itemArchivePath];
    return [NSKeyedArchiver archiveRootObject:allItems toFile:path];
}


 • 在 BNRItemStore.m 中实现这个⽅方法:发送
    archiveRootObject:toFile: 消息到
    NSKeyedArchiver 类
 • archiveRootObject:toFile: ⽅方法负责保存 allItems 中
    每个单独的 BNRItem 到 itemArchivePath
• 这个⽅方法会⾸首先创建⼀一个 NSKeyedArchiver 的实
 例。

• 然后发送 encodeWithCoders: 消息到 root
 object(这⾥里是 allItems )。

• NSKeyedArchiver 是 NSCoder 的⼦子类,所以可以
 传给 encodeWithCoder:
• allItems 收到 encodeWithCoder: 消息后,会再发
 给它包含的所有的对象,传递的同样是
 NSKeyedArchiver。

• 这个数组的内容,也就是⼀一堆 BNRItems,再
 encode 它们的实例变量到同⼀一个
 NSKeyedArchiver。

• ⼀一旦所有这些对象都被编码之后,
 NSKeyedArchiver 就把它收集到的这些数据写到它
 的 path 参数(我们前⾯面构建的路径)
Archiving
applicationDidEnterBackGround:
 • 当⽤用户点击 “Home” 时,
   applicationDidEnterBackground: 消息被发给
   HomepwnerAppDelegate,我们希望在这时候给
   BNRItemStore 发送 saveChanges
 • 我们在 HomepwnerAppDelegate.m 中修改
   applicationDidEnterBackGround: 来启动 BNRItems
   的保存(别忘了导⼊入 BNRItemStore 头⽂文件)
- (void)applicationDidEnterBackground:(UIApplication *)application
{
    BOOL success = [[BNRItemStore defaultStore] saveChanges];
    if (success) {
        NSLog(@"保存所有的 BNRItem ");
    } else {
        NSLog(@"⽆无法保存 BNRItem ");
    }
}
⽂文件写⼊入确认
• 构建并在模拟器中运⾏行,创建⼀一个新的
 BNRItems。然后点击 home 键离开应⽤用,检查控
 制台,我们应该看到前⾯面代码中记录的⽇日志。
• 我们也可以在电脑的⺫⽬目录中确认⼀一下⽂文件是否写
 ⼊入成功。在 Finder 中,Command-Shift-G, 键⼊入“~/
 Library/Application Support/iPhone Simulator”,
 找到应⽤用的 sandbox 查看⼀一下
• 下⼀一步编写加载⽂文件的代码,我们可以在
BNRItemStore 被创建时使⽤用 NSKeyedUnarchiver
unarchiveObjectWithFile:
- (id)init
{
    self = [super init];
    if (self) {
//         allItems = [[NSMutableArray alloc] init];
        NSString *path = [self itemArchivePath];
        allItems = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
        // 如果数组之前没有被保存,创建⼀一个新的空数组
        if (!allItems) {
             allItems = [[NSMutableArray alloc] init];
        }
    }

    return self;
}



• unarchiveObjectWithFile: ⽅方法创建⼀一个
    NSKeyedUnarchiver 实例加载位于 itemArchivePath
    的 archive 到它的实例
• 下⾯面我们来看⼀一下加载的过程。
• 检测 archive 中 root 对象的类型并且⽣生成这个类型
 的实例,在这⾥里,这个类型将是⼀一个
 NSMutableArray,因为我们就是使⽤用这个类型的
 root object 来⽣生成的这个 archive
• 然后新分配的 NSMutableArray 会被发送
 initWithCoder: , NSKeyedUnarchiver 被作为参数传
 ⼊入;

• 然后就是数组开始从 NSKeyedUnarchiver 解码它
 的内容( BNRItem 的实例), 给这些对象发送
 initWithCoder: 消息,传递同样的
 NSKeyedUnarchiver

• 再次构建并运⾏行,测试⼀一下加载功能
不⽣生成随机数据了
    • 因为我们现在已经可以保存和加载 BNRItem 了,
     所以我们可以把⽣生成随机数据的功能删除掉。
    • 在 BNRItemStore.m 中修改 createItem 的实现让它
     ⽣生成⼀一个不带随机数据的空的 BNRItem
- (BNRItem *)createItem
{
//    BNRItem *p = [BNRItem randomItem];
    BNRItem *p = [[BNRItem alloc] init];

     [allItems addObject:p];
     return p;
}
应⽤用程序的 States 和 Transitions
• 在 Hompwner 中,我们是在应⽤用程序进⼊入 backgroud
 state 时 archive BNRItem 的。

• 下⾯面我们来看⼀一下应⽤用程序状态有关的内容,包括这
 些状态是怎么被触发的,以及我们怎么获得通知

• 后⾯面是⼀一个有关状态的概要图
应⽤用状态
Not running
• 不执⾏行任何代码也不占⽤用 RAM 内存
active state
• ⽤用户启动⼀一个应⽤用之后,就进⼊入了 active state 。
• 应⽤用界⾯面在屏幕上显⽰示,正在接收事件,并且它
 的代码在处理这些事件
inactive state
• 在 active state 下,应⽤用可以被系统事件临时性的
 打断,例如 SMS 消息,推送通知,来电,或者
 alarm。应⽤用上⾯面会被叠加⼀一个界⾯面来处理这个事
 件。这种状态被称为 inactive state 。
• 在 inactive state 下,应⽤用⼤大部分是可⻅见的,并且
 在执⾏行代码,但是没有在接收事件。应⽤用⼀一般在
 inactive state 下会停留很短的⼀一个时间。

• 可以通过按下设备顶部的锁定按钮(lock)让活
 动的应⽤用程序强制进⼊入 inactive state

• 直到设备被解锁(unlock)前,应⽤用会⼀一直保持
 inactive
background state
• 当⽤用户按下 home 键或是某种其他⽅方式切换到其
 它应⽤用时,应⽤用进⼊入 background state。
• 在 background state 下,界⾯面不可⻅见,也不能接
 收消息,但是仍然可以执⾏行代码。
• 默认情况下,进⼊入 background state 的应⽤用在进
 ⼊入 suspended state 前有 5 秒的时间。
suspended state
• 在 suspended state 下的应⽤用不能执⾏行代码,⽆无法
 看到它的界⾯面,并且 suspended 时不需要的资源
 都会被销毁。
• suspended 的应⽤用处于冻结状态,当⽤用户重新启
动它的时候,可以被快速的解冻。

• 被销毁的资源都是可以被重新加载的,像缓存的
图⽚片,系统管理的缓存,以及其他图⽚片数据。

• 我们不需要考虑这些资源的销毁和重新记载,应
⽤用程序会⾃自动处理。
应⽤用状态表


                         Receives   Executes
   State       Visible
                          Events      Code
 Not Running     No         No         No


   Active        Yes       Yes        Yes


   Inactive    Mostly       No        Yes


 Background      No         No        Yes


 Suspended       No         No         No
background 时保存数据
• 应⽤用变更状态时,应⽤用 delegate 会被发送⼀一个消
 息。像 application:didFinishLaunchingWithOptions:
 等。
• 我们可以在这些⽅方法中实现代码采取⼀一些适当的
 操作。
• 我们应该在往 background state 转换时保存关键
 的修改和应⽤用程序的状态。

• 因为这是应⽤用程序在进⼊入 suspended state 前还可
 以执⾏行代码的最后时刻。

• ⼀一旦进⼊入了 suspended state,应⽤用有可能会被操
 作系统根据⾃自⾝身的需要终⽌止。
使⽤用 NSData 写⼊入⽂文件系统
• 对 Homepwner 进⾏行 archiving 时我们保存和加载
 了针对每个 BNRItem 的 imageKey。

• 我们可以扩展 image store,在它们被添加时保存
 图⽚片,然后在需要的时候可以提取出来。
• BNRItem 实例的 image 是通过⽤用户交互⽣生成的,
 并且只存储在应⽤用程序内部。

• 这样 Documents ⺫⽬目录就是保存它们的绝好位置。
• ⽤用户采集图⽚片时我们⽣生成了⼀一个唯⼀一的 image
 key,我们可以使⽤用这个 image key 来给⽂文件系统
 的⽂文件命名。
图⽚片路径:imagePathForKey:
     • 在 BNRImageStore.h 中,添加⼀一个新的⽅方法声明
     • 并在 BNRImageStore.m 中实现,可以使⽤用给定的
        key 在 documents ⺫⽬目录⽣生成⼀一个路径
- (NSString *)imagePathForKey:(NSString *)key
{
    NSArray *documentDirectories =
    NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentDirectory = [documentDirectories objectAtIndex:0];

    return [documentDirectory stringByAppendingPathComponent:key];
}
NSData
• 为了保存和加载,我们需要把图⽚片的 JPEG 形式拷
 ⻉贝到内存中的 buffer 。
• NSData 就可以⽅方便的⽣生成,维护和销毁这些类型
 的 buffer。
• ⼀一个 NSData 实例持有⼀一定数量字节数的⼆二进制数
 据。
• 我们可以使⽤用 NSData 存储图⽚片数据。
UIImageJPEGPresentation
 • 在 BNRImageStore.m 中修改 setImage:forKey: ⽅方法
    来获得⼀一个路径,并且保存图⽚片
- (void)setImage:(UIImage *)i forKey:(NSString *)s
{
    [dictionary setObject:i forKey:s];
    NSString *imagePath = [self imagePathForKey:s];
    NSData *d = UIImageJPEGRepresentation(i, 0.5);
    [d writeToFile:imagePath atomically:YES];
}



• UIImageJPEGPresentation 函数的第⼀一个参数是⼀一
  个 UIImage 对象,第⼆二个参数是压缩质量,是⼀一个
  从 0 -1 的 float,1 是最⾼高质量。
• 返回的是 NSData 实例
writeToFile:automatically:
     NSData *d = UIImageJPEGRepresentation(i, 0.5);
     [d writeToFile:imagePath atomically:YES];
}




    • 给 NSData 实例发送 writeToFile:automatically: 消
     息,NSData 就被写⼊入⽂文件系统。
• automatically 是⼀一个 Boolean 值。
• 如果是 YES, ⽂文件会先被写到⽂文件系统的⼀一个临时
 位置,⼀一些写⼊入操作完成,这个⽂文件再被使⽤用它
 的 path 重命名,已经存在的⽂文件会被替掉。
• 设成 automatically 可以防⽌止写⼊入过程中应⽤用崩掉
 引起的数据损坏
同步删除
    • 增加完⽂文件以后,再看⼀一下⽂文件的删除。
    • 在 BNRImageStore.m 中,我们要确保把⽂文件从
     store 中删除的同时,也把它从⽂文件系统中删除。

- (void)deleteImageForkey:(NSString *)s
{
    if (!s) {
        return;
    }
    [dictionary removeObjectForKey:s];

     // 确保⽂文件被从 store 删除的同时,也从⽂文件系统删除
     NSString *path = [self imagePathForKey:s];
     [[NSFileManager defaultManager] removeItemAtPath:path error:NULL];
}
imageWithContentsOfFile:
• 现在⽂文件已经被存储在⽂文件系统了,
 BNRImageStore 在被请求的时候需要加载这些⽂文
 件。
• UIImage 的类⽅方法 imageWithContentsOfFile: 可以
 从⽂文件中作为⼀一个 image 读⼊入。
• 在 BNRImageStore.m 中替换 imageForKey: , 这样
 BNRImageStore 在它没有这个⽂文件的时候就可以
 从⽂文件系统进⾏行加载。
imageForKey:
// 如果还没有⽂文件的话,从⽂文件系统加载
- (UIImage *)imageForKey:(NSString *)s
{
//    return [dictionary objectForKey:s];
    // 如果可能的话,就从字典中获取
    UIImage *result = [dictionary objectForKey:s];

    if (!result) {
        // 从⽂文件创建⼀一个 UIImage
        result = [UIImage imageWithContentsOfFile:[self imagePathForKey:s]];

       // 如果在⽂文件系统找到⼀一个 image,把它放到 cache 中
       if (result) {
           [dictionary setObject:result forKey:s];
       } else {
           NSLog(@"错误:⽆无法找到 %@", [self imagePathForKey:s]);
       }
    }
    return result;
}
• 前⾯面对 image 的增加,删除和记载都完成了,还
     有⼀一个问题是 BNRItem 被从 store 中删除时,⽂文件
     也应该被从⽂文件系统删除。

    • 在 BNRItemStore.m 中,导⼊入 BNRImageStore 的头
     ⽂文件,并且添加下列代码到 removeItem:

- (void)removeItem:(BNRItem *)p
{
    // BNRItem 被从 store 中移除的时候,它的 image 也应该被从⽂文件系统删除
    NSString *key = [p imageKey];
    [[BNRImageStore sharedStore] deleteImageForkey:key];

    [allItems removeObjectIdenticalTo:p];
}

Más contenido relacionado

La actualidad más candente

iOS程序设计-数据持久化
iOS程序设计-数据持久化iOS程序设计-数据持久化
iOS程序设计-数据持久化qiyutan
 
Script with engine
Script with engineScript with engine
Script with engineWebrebuild
 
Docker進階探討
Docker進階探討Docker進階探討
Docker進階探討國昭 張
 
深入淺出 Web 容器 - Tomcat 原始碼分析
深入淺出 Web 容器  - Tomcat 原始碼分析深入淺出 Web 容器  - Tomcat 原始碼分析
深入淺出 Web 容器 - Tomcat 原始碼分析Justin Lin
 
CH04:認識物件
CH04:認識物件CH04:認識物件
CH04:認識物件Justin Lin
 
合久必分,分久必合
合久必分,分久必合合久必分,分久必合
合久必分,分久必合Qiangning Hong
 
04 Delegation and Core Location
04 Delegation and Core Location04 Delegation and Core Location
04 Delegation and Core LocationTom Fan
 
Underscore
UnderscoreUnderscore
Underscorecazhfe
 
KISSY for starter
KISSY for starterKISSY for starter
KISSY for starteryiming he
 
10. 資料永續與交換
10. 資料永續與交換10. 資料永續與交換
10. 資料永續與交換Justin Lin
 
Ch07 使用 JSTL
Ch07 使用 JSTLCh07 使用 JSTL
Ch07 使用 JSTLJustin Lin
 
Kissy editor开发与设计
Kissy editor开发与设计Kissy editor开发与设计
Kissy editor开发与设计yiming he
 
Javascript share
Javascript shareJavascript share
Javascript shareXu Mac
 
CH09:Collection與Map
CH09:Collection與MapCH09:Collection與Map
CH09:Collection與MapJustin Lin
 
Java SE 7 技術手冊投影片第 14 章 - 整合資料庫
Java SE 7 技術手冊投影片第 14 章 - 整合資料庫Java SE 7 技術手冊投影片第 14 章 - 整合資料庫
Java SE 7 技術手冊投影片第 14 章 - 整合資料庫Justin Lin
 
前端MVVM框架安全
前端MVVM框架安全前端MVVM框架安全
前端MVVM框架安全Borg Han
 

La actualidad más candente (20)

iOS程序设计-数据持久化
iOS程序设计-数据持久化iOS程序设计-数据持久化
iOS程序设计-数据持久化
 
J Query Learn
J Query LearnJ Query Learn
J Query Learn
 
Script with engine
Script with engineScript with engine
Script with engine
 
Docker進階探討
Docker進階探討Docker進階探討
Docker進階探討
 
深入淺出 Web 容器 - Tomcat 原始碼分析
深入淺出 Web 容器  - Tomcat 原始碼分析深入淺出 Web 容器  - Tomcat 原始碼分析
深入淺出 Web 容器 - Tomcat 原始碼分析
 
CH04:認識物件
CH04:認識物件CH04:認識物件
CH04:認識物件
 
合久必分,分久必合
合久必分,分久必合合久必分,分久必合
合久必分,分久必合
 
04 Delegation and Core Location
04 Delegation and Core Location04 Delegation and Core Location
04 Delegation and Core Location
 
Underscore
UnderscoreUnderscore
Underscore
 
KISSY for starter
KISSY for starterKISSY for starter
KISSY for starter
 
A
AA
A
 
10. 資料永續與交換
10. 資料永續與交換10. 資料永續與交換
10. 資料永續與交換
 
Ch07 使用 JSTL
Ch07 使用 JSTLCh07 使用 JSTL
Ch07 使用 JSTL
 
AJAX Basic
AJAX BasicAJAX Basic
AJAX Basic
 
Kissy editor开发与设计
Kissy editor开发与设计Kissy editor开发与设计
Kissy editor开发与设计
 
Javascript share
Javascript shareJavascript share
Javascript share
 
CH09:Collection與Map
CH09:Collection與MapCH09:Collection與Map
CH09:Collection與Map
 
Java SE 7 技術手冊投影片第 14 章 - 整合資料庫
Java SE 7 技術手冊投影片第 14 章 - 整合資料庫Java SE 7 技術手冊投影片第 14 章 - 整合資料庫
Java SE 7 技術手冊投影片第 14 章 - 整合資料庫
 
Docker實務
Docker實務Docker實務
Docker實務
 
前端MVVM框架安全
前端MVVM框架安全前端MVVM框架安全
前端MVVM框架安全
 

Similar a 14 Saving Loading and Application States

喬叔 Elasticsearch Index 管理技巧與效能優化
喬叔 Elasticsearch Index 管理技巧與效能優化喬叔 Elasticsearch Index 管理技巧與效能優化
喬叔 Elasticsearch Index 管理技巧與效能優化Joe Wu
 
美团点评技术沙龙14:美团云对象存储系统
美团点评技术沙龙14:美团云对象存储系统美团点评技术沙龙14:美团云对象存储系统
美团点评技术沙龙14:美团云对象存储系统美团点评技术团队
 
Java SE 7 技術手冊投影片第 05 章 - 物件封裝
Java SE 7 技術手冊投影片第 05 章  - 物件封裝Java SE 7 技術手冊投影片第 05 章  - 物件封裝
Java SE 7 技術手冊投影片第 05 章 - 物件封裝Justin Lin
 
MySQL自动切换设计与实现
MySQL自动切换设计与实现MySQL自动切换设计与实现
MySQL自动切换设计与实现orczhou
 
主库自动切换 V2.0
主库自动切换 V2.0主库自动切换 V2.0
主库自动切换 V2.0jinqing zhu
 
ElasticSearch Training#2 (advanced concepts)-ESCC#1
ElasticSearch Training#2 (advanced concepts)-ESCC#1ElasticSearch Training#2 (advanced concepts)-ESCC#1
ElasticSearch Training#2 (advanced concepts)-ESCC#1medcl
 
12 Camera
12 Camera12 Camera
12 CameraTom Fan
 
Ch03 請求與回應
Ch03 請求與回應Ch03 請求與回應
Ch03 請求與回應Justin Lin
 
IOS入门分享
IOS入门分享IOS入门分享
IOS入门分享zenyuhao
 
对MySQL应用的一些总结
对MySQL应用的一些总结对MySQL应用的一些总结
对MySQL应用的一些总结Lixun Peng
 
Couchbase introduction - Chinese
Couchbase introduction - Chinese Couchbase introduction - Chinese
Couchbase introduction - Chinese Vickie Zeng
 
Nosql及其主要产品简介
Nosql及其主要产品简介Nosql及其主要产品简介
Nosql及其主要产品简介振林 谭
 
開發人員必須知道的 Kubernetes 核心技術 - Kubernetes Summit 2018
開發人員必須知道的 Kubernetes 核心技術 - Kubernetes Summit 2018開發人員必須知道的 Kubernetes 核心技術 - Kubernetes Summit 2018
開發人員必須知道的 Kubernetes 核心技術 - Kubernetes Summit 2018Will Huang
 
Servlet & JSP 教學手冊第二版 - 第 7 章:使用 JSTL
Servlet & JSP 教學手冊第二版 - 第 7 章:使用 JSTLServlet & JSP 教學手冊第二版 - 第 7 章:使用 JSTL
Servlet & JSP 教學手冊第二版 - 第 7 章:使用 JSTLJustin Lin
 
賽門鐵克 Storage Foundation 6.0 簡報
賽門鐵克 Storage Foundation 6.0 簡報賽門鐵克 Storage Foundation 6.0 簡報
賽門鐵克 Storage Foundation 6.0 簡報Wales Chen
 
以Code igniter為基礎的網頁前端程式設計
以Code igniter為基礎的網頁前端程式設計以Code igniter為基礎的網頁前端程式設計
以Code igniter為基礎的網頁前端程式設計Amigo 陳兆祥
 
4 葉金榮-my sql優化 - 20151219
4 葉金榮-my sql優化 - 201512194 葉金榮-my sql優化 - 20151219
4 葉金榮-my sql優化 - 20151219Ivan Tu
 

Similar a 14 Saving Loading and Application States (20)

喬叔 Elasticsearch Index 管理技巧與效能優化
喬叔 Elasticsearch Index 管理技巧與效能優化喬叔 Elasticsearch Index 管理技巧與效能優化
喬叔 Elasticsearch Index 管理技巧與效能優化
 
美团点评技术沙龙14:美团云对象存储系统
美团点评技术沙龙14:美团云对象存储系统美团点评技术沙龙14:美团云对象存储系统
美团点评技术沙龙14:美团云对象存储系统
 
Java SE 7 技術手冊投影片第 05 章 - 物件封裝
Java SE 7 技術手冊投影片第 05 章  - 物件封裝Java SE 7 技術手冊投影片第 05 章  - 物件封裝
Java SE 7 技術手冊投影片第 05 章 - 物件封裝
 
MySQL自动切换设计与实现
MySQL自动切换设计与实现MySQL自动切换设计与实现
MySQL自动切换设计与实现
 
主库自动切换 V2.0
主库自动切换 V2.0主库自动切换 V2.0
主库自动切换 V2.0
 
ElasticSearch Training#2 (advanced concepts)-ESCC#1
ElasticSearch Training#2 (advanced concepts)-ESCC#1ElasticSearch Training#2 (advanced concepts)-ESCC#1
ElasticSearch Training#2 (advanced concepts)-ESCC#1
 
Zabbix in PPTV
Zabbix in PPTVZabbix in PPTV
Zabbix in PPTV
 
12 Camera
12 Camera12 Camera
12 Camera
 
Ch03 請求與回應
Ch03 請求與回應Ch03 請求與回應
Ch03 請求與回應
 
IOS入门分享
IOS入门分享IOS入门分享
IOS入门分享
 
对MySQL应用的一些总结
对MySQL应用的一些总结对MySQL应用的一些总结
对MySQL应用的一些总结
 
Couchbase introduction - Chinese
Couchbase introduction - Chinese Couchbase introduction - Chinese
Couchbase introduction - Chinese
 
Nosql及其主要产品简介
Nosql及其主要产品简介Nosql及其主要产品简介
Nosql及其主要产品简介
 
開發人員必須知道的 Kubernetes 核心技術 - Kubernetes Summit 2018
開發人員必須知道的 Kubernetes 核心技術 - Kubernetes Summit 2018開發人員必須知道的 Kubernetes 核心技術 - Kubernetes Summit 2018
開發人員必須知道的 Kubernetes 核心技術 - Kubernetes Summit 2018
 
Servlet & JSP 教學手冊第二版 - 第 7 章:使用 JSTL
Servlet & JSP 教學手冊第二版 - 第 7 章:使用 JSTLServlet & JSP 教學手冊第二版 - 第 7 章:使用 JSTL
Servlet & JSP 教學手冊第二版 - 第 7 章:使用 JSTL
 
賽門鐵克 Storage Foundation 6.0 簡報
賽門鐵克 Storage Foundation 6.0 簡報賽門鐵克 Storage Foundation 6.0 簡報
賽門鐵克 Storage Foundation 6.0 簡報
 
以Code igniter為基礎的網頁前端程式設計
以Code igniter為基礎的網頁前端程式設計以Code igniter為基礎的網頁前端程式設計
以Code igniter為基礎的網頁前端程式設計
 
Why use MySQL
Why use MySQLWhy use MySQL
Why use MySQL
 
物件封裝
物件封裝物件封裝
物件封裝
 
4 葉金榮-my sql優化 - 20151219
4 葉金榮-my sql優化 - 201512194 葉金榮-my sql優化 - 20151219
4 葉金榮-my sql優化 - 20151219
 

Más de Tom Fan

PhoneGap 通信原理和插件系统
PhoneGap 通信原理和插件系统PhoneGap 通信原理和插件系统
PhoneGap 通信原理和插件系统Tom Fan
 
HTML5 Web workers
HTML5 Web workersHTML5 Web workers
HTML5 Web workersTom Fan
 
Web sockets
Web socketsWeb sockets
Web socketsTom Fan
 
Semantics
SemanticsSemantics
SemanticsTom Fan
 
Multimedia
MultimediaMultimedia
MultimediaTom Fan
 
Intro to-html5
Intro to-html5Intro to-html5
Intro to-html5Tom Fan
 
Html5 history
Html5 historyHtml5 history
Html5 historyTom Fan
 
Geolocation
GeolocationGeolocation
GeolocationTom Fan
 
File api
File apiFile api
File apiTom Fan
 
Deviceaccess
DeviceaccessDeviceaccess
DeviceaccessTom Fan
 
Webstorage
WebstorageWebstorage
WebstorageTom Fan
 
Html5 最重要的部分
Html5 最重要的部分Html5 最重要的部分
Html5 最重要的部分Tom Fan
 
AT&T 的 HTML5 策略和应用现状
AT&T 的 HTML5 策略和应用现状AT&T 的 HTML5 策略和应用现状
AT&T 的 HTML5 策略和应用现状Tom Fan
 
PhoneGap 2.0 开发
PhoneGap 2.0 开发PhoneGap 2.0 开发
PhoneGap 2.0 开发Tom Fan
 
Android 平台 HTML5 应用开发
Android 平台 HTML5 应用开发Android 平台 HTML5 应用开发
Android 平台 HTML5 应用开发Tom Fan
 
HTML5 生态系统和应用架构模型
HTML5 生态系统和应用架构模型HTML5 生态系统和应用架构模型
HTML5 生态系统和应用架构模型Tom Fan
 
18 NSUserDefaults
18 NSUserDefaults18 NSUserDefaults
18 NSUserDefaultsTom Fan
 
15 Subclassing UITableViewCell
15 Subclassing UITableViewCell15 Subclassing UITableViewCell
15 Subclassing UITableViewCellTom Fan
 

Más de Tom Fan (20)

PhoneGap 通信原理和插件系统
PhoneGap 通信原理和插件系统PhoneGap 通信原理和插件系统
PhoneGap 通信原理和插件系统
 
HTML5 Web workers
HTML5 Web workersHTML5 Web workers
HTML5 Web workers
 
Web sockets
Web socketsWeb sockets
Web sockets
 
Storage
StorageStorage
Storage
 
Semantics
SemanticsSemantics
Semantics
 
Multimedia
MultimediaMultimedia
Multimedia
 
Intro to-html5
Intro to-html5Intro to-html5
Intro to-html5
 
Html5 history
Html5 historyHtml5 history
Html5 history
 
Geolocation
GeolocationGeolocation
Geolocation
 
File api
File apiFile api
File api
 
Deviceaccess
DeviceaccessDeviceaccess
Deviceaccess
 
Css3
Css3Css3
Css3
 
Webstorage
WebstorageWebstorage
Webstorage
 
Html5 最重要的部分
Html5 最重要的部分Html5 最重要的部分
Html5 最重要的部分
 
AT&T 的 HTML5 策略和应用现状
AT&T 的 HTML5 策略和应用现状AT&T 的 HTML5 策略和应用现状
AT&T 的 HTML5 策略和应用现状
 
PhoneGap 2.0 开发
PhoneGap 2.0 开发PhoneGap 2.0 开发
PhoneGap 2.0 开发
 
Android 平台 HTML5 应用开发
Android 平台 HTML5 应用开发Android 平台 HTML5 应用开发
Android 平台 HTML5 应用开发
 
HTML5 生态系统和应用架构模型
HTML5 生态系统和应用架构模型HTML5 生态系统和应用架构模型
HTML5 生态系统和应用架构模型
 
18 NSUserDefaults
18 NSUserDefaults18 NSUserDefaults
18 NSUserDefaults
 
15 Subclassing UITableViewCell
15 Subclassing UITableViewCell15 Subclassing UITableViewCell
15 Subclassing UITableViewCell
 

14 Saving Loading and Application States

  • 2. • 在⼀一个 iOS application 中保存和加载数据有很多 ⽅方法。 • 这⼀一个主题我们来看⼀一下⼀一些最常⻅见的机制,以 及在 iOS 上对⽂文件系统进⾏行读写需要理解的概念
  • 4. • 任何 iOS 应⽤用实际都在做⼀一件事情:提供给⽤用户 ⼀一个界⾯面让他们来操作数据。 • 在应⽤用中的每⼀一个对象在这个过程中都扮演下⾯面 的⼀一个⾓角⾊色。 • Model 对象,负责持有⽤用户操作的数据 • View 对象,只是体现这些数据 • Controllers,负责应⽤用运⾏行到底是怎么回事 • 因此当我们讨论保存和加载数据时,我们⼏几乎总 是在谈论保存和加载 Model 对象
  • 5. • 在 Homepwner 中,⽤用户操作的模型对象是 BNRItem 的实例。 • 如果在应⽤用退出后再启动时 BNRItem 的实例能够 持久存在,Homepwner 将真正成为⼀一个有⽤用的应 ⽤用 • 在这⼀一章,我们将使⽤用 archiving 来保存和加载 BNRItem
  • 6. • Archiving 是在 iOS 上持久化模型数据的⼀一种最常 ⻅见的⽅方法。 • Archiving ⼀一个对象会记录它所有的实例变量并且 把他们保存到⽂文件系统。 • Unarchiving ⼀一个对象就是从⽂文件系统加载数据, 并且再从这个记录⽣生成对象。
  • 7. NSCoding • 需要把它的实例进⾏行 archive 和 unarchive 的类必 须符合 NSCoding protocol。 • 并且实现它的两个必须的⽅方法, encodeWithCoder: 和 initWithCoder: @protocol NSCoding - (void)encodeWithCoder:(NSCoder *)aCoder; - (id)initWithCoder:(NSCoder *)aDecoder;
  • 8. encode • 在 BNRItem.h 中增加 NSCoding protocol 的声明 @interface BNRItem : NSObject <NSCoding> • 然后是实现两个必须的⽅方法,⾸首先是 encodeWithCoder:
  • 9. • 当 BNRItem 被发送 encodeWithCoder: 消息时,它 将把它的所有的实例变量编码到作为⼀一个参数传 ⼊入的 NSCoder 对象中 • 我们可以把 NSCoder 对象想成⼀一个数据容器,负 责组织这些数据 • NSCoder 以key-value pair 的⽅方式组织数据
  • 10. encodeWithCoder: • 在 BNRItem.m 中实现 encodeWithCoder: 以添加实 例变量到这个容器 - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:itemName forKey:@"itemName"]; [aCoder encodeObject:serialNumber forKey:@"serialNumber"]; [aCoder encodeObject:dateCreated forKey:@"dateCreated"]; [aCoder encodeObject:imageKey forKey:@"imageKey"]; [aCoder encodeInt:valueInDollars forKey:@"valueInDollars"]; }
  • 11. encodeXXX:forKey • 指向对象的指针使⽤用 encodeObject:forKey: 编码 • valueInDollars 使⽤用 encodeInt:forKey: 编码,还有 很可以 encode 的类型
  • 12. • 不管被编码的值是什么类型,总有⼀一个 key 存在 • key 是⼀一个⽤用来标识哪个实例变量在被编码的字符 串。按照惯例,这个 key 就是被编码的实例变量 的名字
  • 13. 编码对象的递归过程 • ⼀一个对象被编码时,被发送 encodeWithCoder: 。 当⼀一个对象被发送 encodeWithCoder:,它⽤用同样 的⽅方式编码它的实例变量 - 也是给它们发送 encodeWithCoder: • 这样编码⼀一个对象就是⼀一个对象编码其他对象的 递归过程
  • 15. initWithCoder: • encoding 时使⽤用 key 的⺫⽬目的是为了之后在 BNRItem 被从⽂文件系统加载时⽤用来获取已编码的 值 • 被从⼀一个 archive 中加载的对象被发送的是 initWithCoder: 消息。 • 这个⽅方法攫取所有在 encodeWithCoder: 中被编码 的对象,并且把它们分配给合适的实例变量。 • 在 BNRItem.m 中实现这个⽅方法
  • 16. decodeXXXForKey: - (id)initWithCoder:(NSCoder *)aDecoder { self = [super init]; if (self) { [self setItemName:[aDecoder decodeObjectForKey:@"itemName"]]; [self setSerialNumber:[aDecoder decodeObjectForKey:@"serialNumber"]]; [self setImageKey:[aDecoder decodeObjectForKey:@"imageKey"]]; [self setValueInDollars:[aDecoder decodeIntForKey:@"valueInDollars"]]; dateCreated = [aDecoder decodeObjectForKey:@"dateCreated"]; } return self; } • 这个⽅方法也有⼀一个 NSCoder 参数,它⾥里⾯面的数据 是供正在被初始化的 BNRItem 使⽤用的 • 使⽤用 decodeObjectForKey: 取回对象(object), decodeIntForKey: 取回 valueInDollar
  • 17. • 经过前⾯面的改造之后,BNRItem 现在是 NSCoding 兼容的了,⽽而且也可以使⽤用 archiving 从⽂文件系统 进⾏行保存和加载。 • 现在还有两个问题要考虑: • 我们需要⼀一种⽅方法来开启保存和加载操作?(现在只 是可以 archiving 和 unarchiving) • 另外,我们需要在⽂文件系统找⼀一个地⽅方来存储我们要 保存的 BNRItem
  • 19. • 每⼀一个 iOS 应⽤用都有它⾃自⼰己的 applicaton sandbox(应⽤用沙箱)。 • ⼀一个 application sandbox 是在⽂文件系统上的⼀一个 和⽂文件系统其余部分隔离的⺫⽬目录。 • 应⽤用必须位于这个 sandbox 内,并且没有其他的 应⽤用可以访问你的 sandbox。
  • 21. application sandbox • Library/Preferences/ • tmp/ • Documents/ • Library/Caches
  • 22. Library/Preferences/ • 所有的⾸首选项都存在这⾥里;“Settings”应⽤用也在这 ⾥里查找应⽤用程序⾸首选项 • Library/Preferences 被 NSUserDefaults 类⾃自动化的 进⾏行处理 • 当设备和 iTunes 或 iCloud 同步时会被备份
  • 23. tmp/ • ⽤用于写⼊入应⽤用运⾏行时临时⽤用到的数据,⽤用完以后 应该从这个⺫⽬目录删除 • 不备份 • 使⽤用函数 NSTemporaryDirectory 拿到 application sandbox 中 tmp ⺫⽬目录的路径
  • 25. Library/Caches • 应⽤用程序⽣生成的,还希望能够持久存在 • 不会被备份 • 不被备份的⼀一个主要的原因就是这些⽂文件可能会 很⼤大,会延⻓长同步设备的时间
  • 26. 构造⼀一个⽂文件路径 • 来⾃自 Homepwner 的 BNRItem 将被保存到 Documents ⺫⽬目录中⼀一个单独的⽂文件中 • BNRItemStore 将处理⽂文件写⼊入和读取的⼯工作 • ⾸首先,我们需要来构建这个⽂文件的路径 • 在 BNRItemStore 中声明和实现⼀一个新的⽅方法
  • 27. - (NSString *)itemArchivePath { NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentDirectory = [documentDirectories objectAtIndex:0]; return [documentDirectory stringByAppendingPathComponent:@"items.archive"]; } • NSSearchPathForDirectoriesInDomains 函数搜索 ⽂文件系统来查找符合给定参数标准的路径 • 在 iOS 上,最后两个参数总是⼀一样的 • 第⼀一个参数是指定了在 sandbox 中⺫⽬目录的常量。 ⽐比如搜索 NSCachesDirectory 将会返回在应⽤用的 sandbox 中的 Caches ⺫⽬目录 • 返回值是⼀一个字符串数组,在 iOS 上只有⼀一个, 所以只取第⼀一个就可以了
  • 29. • 现在我们已经有了⼀一个⽂文件系统的路径,同时模 型对象(BNRItem)也可以被保存到⽂文件系统了 • 我们可以使⽤用 NSKeyedArchiver 在应⽤用 “退出” 时 保存 BNRItems • 在 BNRItemStore.h 中声明⼀一个新的⽅方法: - (BOOL)saveChanges;
  • 30. archiveRootObject: - (BOOL)saveChanges { // 返回成功或失败 NSString *path = [self itemArchivePath]; return [NSKeyedArchiver archiveRootObject:allItems toFile:path]; } • 在 BNRItemStore.m 中实现这个⽅方法:发送 archiveRootObject:toFile: 消息到 NSKeyedArchiver 类 • archiveRootObject:toFile: ⽅方法负责保存 allItems 中 每个单独的 BNRItem 到 itemArchivePath
  • 31. • 这个⽅方法会⾸首先创建⼀一个 NSKeyedArchiver 的实 例。 • 然后发送 encodeWithCoders: 消息到 root object(这⾥里是 allItems )。 • NSKeyedArchiver 是 NSCoder 的⼦子类,所以可以 传给 encodeWithCoder:
  • 32. • allItems 收到 encodeWithCoder: 消息后,会再发 给它包含的所有的对象,传递的同样是 NSKeyedArchiver。 • 这个数组的内容,也就是⼀一堆 BNRItems,再 encode 它们的实例变量到同⼀一个 NSKeyedArchiver。 • ⼀一旦所有这些对象都被编码之后, NSKeyedArchiver 就把它收集到的这些数据写到它 的 path 参数(我们前⾯面构建的路径)
  • 34. applicationDidEnterBackGround: • 当⽤用户点击 “Home” 时, applicationDidEnterBackground: 消息被发给 HomepwnerAppDelegate,我们希望在这时候给 BNRItemStore 发送 saveChanges • 我们在 HomepwnerAppDelegate.m 中修改 applicationDidEnterBackGround: 来启动 BNRItems 的保存(别忘了导⼊入 BNRItemStore 头⽂文件) - (void)applicationDidEnterBackground:(UIApplication *)application { BOOL success = [[BNRItemStore defaultStore] saveChanges]; if (success) { NSLog(@"保存所有的 BNRItem "); } else { NSLog(@"⽆无法保存 BNRItem "); } }
  • 35. ⽂文件写⼊入确认 • 构建并在模拟器中运⾏行,创建⼀一个新的 BNRItems。然后点击 home 键离开应⽤用,检查控 制台,我们应该看到前⾯面代码中记录的⽇日志。 • 我们也可以在电脑的⺫⽬目录中确认⼀一下⽂文件是否写 ⼊入成功。在 Finder 中,Command-Shift-G, 键⼊入“~/ Library/Application Support/iPhone Simulator”, 找到应⽤用的 sandbox 查看⼀一下
  • 37. unarchiveObjectWithFile: - (id)init { self = [super init]; if (self) { // allItems = [[NSMutableArray alloc] init]; NSString *path = [self itemArchivePath]; allItems = [NSKeyedUnarchiver unarchiveObjectWithFile:path]; // 如果数组之前没有被保存,创建⼀一个新的空数组 if (!allItems) { allItems = [[NSMutableArray alloc] init]; } } return self; } • unarchiveObjectWithFile: ⽅方法创建⼀一个 NSKeyedUnarchiver 实例加载位于 itemArchivePath 的 archive 到它的实例
  • 38. • 下⾯面我们来看⼀一下加载的过程。 • 检测 archive 中 root 对象的类型并且⽣生成这个类型 的实例,在这⾥里,这个类型将是⼀一个 NSMutableArray,因为我们就是使⽤用这个类型的 root object 来⽣生成的这个 archive
  • 39. • 然后新分配的 NSMutableArray 会被发送 initWithCoder: , NSKeyedUnarchiver 被作为参数传 ⼊入; • 然后就是数组开始从 NSKeyedUnarchiver 解码它 的内容( BNRItem 的实例), 给这些对象发送 initWithCoder: 消息,传递同样的 NSKeyedUnarchiver • 再次构建并运⾏行,测试⼀一下加载功能
  • 40. 不⽣生成随机数据了 • 因为我们现在已经可以保存和加载 BNRItem 了, 所以我们可以把⽣生成随机数据的功能删除掉。 • 在 BNRItemStore.m 中修改 createItem 的实现让它 ⽣生成⼀一个不带随机数据的空的 BNRItem - (BNRItem *)createItem { // BNRItem *p = [BNRItem randomItem]; BNRItem *p = [[BNRItem alloc] init]; [allItems addObject:p]; return p; }
  • 42. • 在 Hompwner 中,我们是在应⽤用程序进⼊入 backgroud state 时 archive BNRItem 的。 • 下⾯面我们来看⼀一下应⽤用程序状态有关的内容,包括这 些状态是怎么被触发的,以及我们怎么获得通知 • 后⾯面是⼀一个有关状态的概要图
  • 45. active state • ⽤用户启动⼀一个应⽤用之后,就进⼊入了 active state 。 • 应⽤用界⾯面在屏幕上显⽰示,正在接收事件,并且它 的代码在处理这些事件
  • 46. inactive state • 在 active state 下,应⽤用可以被系统事件临时性的 打断,例如 SMS 消息,推送通知,来电,或者 alarm。应⽤用上⾯面会被叠加⼀一个界⾯面来处理这个事 件。这种状态被称为 inactive state 。
  • 47. • 在 inactive state 下,应⽤用⼤大部分是可⻅见的,并且 在执⾏行代码,但是没有在接收事件。应⽤用⼀一般在 inactive state 下会停留很短的⼀一个时间。 • 可以通过按下设备顶部的锁定按钮(lock)让活 动的应⽤用程序强制进⼊入 inactive state • 直到设备被解锁(unlock)前,应⽤用会⼀一直保持 inactive
  • 48. background state • 当⽤用户按下 home 键或是某种其他⽅方式切换到其 它应⽤用时,应⽤用进⼊入 background state。 • 在 background state 下,界⾯面不可⻅见,也不能接 收消息,但是仍然可以执⾏行代码。 • 默认情况下,进⼊入 background state 的应⽤用在进 ⼊入 suspended state 前有 5 秒的时间。
  • 49. suspended state • 在 suspended state 下的应⽤用不能执⾏行代码,⽆无法 看到它的界⾯面,并且 suspended 时不需要的资源 都会被销毁。
  • 50. • suspended 的应⽤用处于冻结状态,当⽤用户重新启 动它的时候,可以被快速的解冻。 • 被销毁的资源都是可以被重新加载的,像缓存的 图⽚片,系统管理的缓存,以及其他图⽚片数据。 • 我们不需要考虑这些资源的销毁和重新记载,应 ⽤用程序会⾃自动处理。
  • 51. 应⽤用状态表 Receives Executes State Visible Events Code Not Running No No No Active Yes Yes Yes Inactive Mostly No Yes Background No No Yes Suspended No No No
  • 52. background 时保存数据 • 应⽤用变更状态时,应⽤用 delegate 会被发送⼀一个消 息。像 application:didFinishLaunchingWithOptions: 等。 • 我们可以在这些⽅方法中实现代码采取⼀一些适当的 操作。
  • 53. • 我们应该在往 background state 转换时保存关键 的修改和应⽤用程序的状态。 • 因为这是应⽤用程序在进⼊入 suspended state 前还可 以执⾏行代码的最后时刻。 • ⼀一旦进⼊入了 suspended state,应⽤用有可能会被操 作系统根据⾃自⾝身的需要终⽌止。
  • 55. • 对 Homepwner 进⾏行 archiving 时我们保存和加载 了针对每个 BNRItem 的 imageKey。 • 我们可以扩展 image store,在它们被添加时保存 图⽚片,然后在需要的时候可以提取出来。
  • 56. • BNRItem 实例的 image 是通过⽤用户交互⽣生成的, 并且只存储在应⽤用程序内部。 • 这样 Documents ⺫⽬目录就是保存它们的绝好位置。 • ⽤用户采集图⽚片时我们⽣生成了⼀一个唯⼀一的 image key,我们可以使⽤用这个 image key 来给⽂文件系统 的⽂文件命名。
  • 57. 图⽚片路径:imagePathForKey: • 在 BNRImageStore.h 中,添加⼀一个新的⽅方法声明 • 并在 BNRImageStore.m 中实现,可以使⽤用给定的 key 在 documents ⺫⽬目录⽣生成⼀一个路径 - (NSString *)imagePathForKey:(NSString *)key { NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentDirectory = [documentDirectories objectAtIndex:0]; return [documentDirectory stringByAppendingPathComponent:key]; }
  • 58. NSData • 为了保存和加载,我们需要把图⽚片的 JPEG 形式拷 ⻉贝到内存中的 buffer 。 • NSData 就可以⽅方便的⽣生成,维护和销毁这些类型 的 buffer。 • ⼀一个 NSData 实例持有⼀一定数量字节数的⼆二进制数 据。 • 我们可以使⽤用 NSData 存储图⽚片数据。
  • 59. UIImageJPEGPresentation • 在 BNRImageStore.m 中修改 setImage:forKey: ⽅方法 来获得⼀一个路径,并且保存图⽚片 - (void)setImage:(UIImage *)i forKey:(NSString *)s { [dictionary setObject:i forKey:s]; NSString *imagePath = [self imagePathForKey:s]; NSData *d = UIImageJPEGRepresentation(i, 0.5); [d writeToFile:imagePath atomically:YES]; } • UIImageJPEGPresentation 函数的第⼀一个参数是⼀一 个 UIImage 对象,第⼆二个参数是压缩质量,是⼀一个 从 0 -1 的 float,1 是最⾼高质量。 • 返回的是 NSData 实例
  • 60. writeToFile:automatically: NSData *d = UIImageJPEGRepresentation(i, 0.5); [d writeToFile:imagePath atomically:YES]; } • 给 NSData 实例发送 writeToFile:automatically: 消 息,NSData 就被写⼊入⽂文件系统。
  • 61. • automatically 是⼀一个 Boolean 值。 • 如果是 YES, ⽂文件会先被写到⽂文件系统的⼀一个临时 位置,⼀一些写⼊入操作完成,这个⽂文件再被使⽤用它 的 path 重命名,已经存在的⽂文件会被替掉。 • 设成 automatically 可以防⽌止写⼊入过程中应⽤用崩掉 引起的数据损坏
  • 62. 同步删除 • 增加完⽂文件以后,再看⼀一下⽂文件的删除。 • 在 BNRImageStore.m 中,我们要确保把⽂文件从 store 中删除的同时,也把它从⽂文件系统中删除。 - (void)deleteImageForkey:(NSString *)s { if (!s) { return; } [dictionary removeObjectForKey:s]; // 确保⽂文件被从 store 删除的同时,也从⽂文件系统删除 NSString *path = [self imagePathForKey:s]; [[NSFileManager defaultManager] removeItemAtPath:path error:NULL]; }
  • 63. imageWithContentsOfFile: • 现在⽂文件已经被存储在⽂文件系统了, BNRImageStore 在被请求的时候需要加载这些⽂文 件。 • UIImage 的类⽅方法 imageWithContentsOfFile: 可以 从⽂文件中作为⼀一个 image 读⼊入。 • 在 BNRImageStore.m 中替换 imageForKey: , 这样 BNRImageStore 在它没有这个⽂文件的时候就可以 从⽂文件系统进⾏行加载。
  • 64. imageForKey: // 如果还没有⽂文件的话,从⽂文件系统加载 - (UIImage *)imageForKey:(NSString *)s { // return [dictionary objectForKey:s]; // 如果可能的话,就从字典中获取 UIImage *result = [dictionary objectForKey:s]; if (!result) { // 从⽂文件创建⼀一个 UIImage result = [UIImage imageWithContentsOfFile:[self imagePathForKey:s]]; // 如果在⽂文件系统找到⼀一个 image,把它放到 cache 中 if (result) { [dictionary setObject:result forKey:s]; } else { NSLog(@"错误:⽆无法找到 %@", [self imagePathForKey:s]); } } return result; }
  • 65. • 前⾯面对 image 的增加,删除和记载都完成了,还 有⼀一个问题是 BNRItem 被从 store 中删除时,⽂文件 也应该被从⽂文件系统删除。 • 在 BNRItemStore.m 中,导⼊入 BNRImageStore 的头 ⽂文件,并且添加下列代码到 removeItem: - (void)removeItem:(BNRItem *)p { // BNRItem 被从 store 中移除的时候,它的 image 也应该被从⽂文件系统删除 NSString *key = [p imageKey]; [[BNRImageStore sharedStore] deleteImageForkey:key]; [allItems removeObjectIdenticalTo:p]; }