首页 > 代码库 > iOS 保存、读取与应用状态

iOS 保存、读取与应用状态

  • 固化

  对于大多数iOS应用,可以将其功能总结为:提供一套界面,帮助用户管理特定的数据。在这一过程中,不同类型的对象要各司其职:模型对象负责保存数据,视图对象负责显示数据,控制器对象负责在模型对象与视图对象之间同步数据。因此,当某个应用要保存和读取数据时,通常要完成的任务是保存和读取相应的模型对象。

  对 JXHmoepwner 应用,用户可以管理的模型对象是 JXItem 对象。目前 JXHomepwner 不嗯给你保存 JXItem 对象,所以,当用户重新运行 JXHomepwner 时,之前创建的 JXItem 对象都会消失。

  固化是由iOS SDK 提供的一种保存和读取对象的机制,使用非常广泛。当应用固化某个对象时,会将该对象的所有属性存入指定文件。当应用解固( unarchive )某个对象时,会从指定的文件读取相应的数据,然后根据数据还原对象。

  为了能够固化或者解固某个对象,相应对象的类必须遵守  NSCoding  协议,并且实现两个必须的方法: encodeWithCoder: 和 initWithCoder: ,代码如下:

#import <Foundation/Foundation.h>

@interface JXItem : NSObject<NSCoding>
/** 创建日期 */
@property (nonatomic,strong,readonly) NSDate * createDate;
/** 名称 */
@property (nonatomic,strong) NSString * itemName;
/** 编号 */
@property (nonatomic,strong) NSString * serialnumber;
/** 价值 */
@property (nonatomic,assign) NSInteger valueInDollars;
/** JXImageStore中的键 */
@property (nonatomic,strong) NSString * itemKey;

+ (instancetype)randomItem;

/**
 *  JXItem类指定的初始化方法
 *  @return 类对象
 */
- (instancetype)initWithItemName:(NSString *)name
                  valueInDollars:(NSInteger)value
                    serialNumber:(NSString *)sNumber;

- (instancetype)initWithItemName:(NSString *)name;
@end

  下面为 JXItem 实现  NSCoding 协议的两个必须方法。先实现 encodeWithCoder:(NSCoder *)aCoder 方法。他有一个类型为 NSCoder 的参数,JXItem 的  encodeWithCoder:(NSCoder *)aCoder 方法要将所有的属性都编码至该参数。在固化过程中,NSCoder 会将 JXItem 转换为键值对形式的数据并写入指定的文件。

#import "JXItem.h"

@implementation JXItem

+ (instancetype)randomItem {
    // 创建不可变数组对象,包含三个形容词
    NSArray * randomAdjectiveList = @[
                                      @"Fluffy",
                                      @"Rusty",
                                      @"Shiny"
                                      ];
    // 创建不可变数组对象,包含三个名词
    NSArray * randomNounList = @[
                                 @"Bear",
                                 @"Spork",
                                 @"Mac"
                                 ];
    
    // 根据数组对象所含的对象的个数,得到随机索引
    // 注意:运算符%是模运算符,运算后得到的是余数
    NSInteger adjectiveIndex = arc4random() % randomAdjectiveList.count;
    NSInteger nounIndex = arc4random() % randomNounList.count;
    // 注意,类型为NSInteger 的变量不是对象
    NSString * randomName = [NSString stringWithFormat:@"%@ %@",randomAdjectiveList[adjectiveIndex],randomNounList[nounIndex]];
    
    NSInteger randomValue = arc4random_uniform(100);
    
    NSString * randomSerialNumber = [NSString stringWithFormat:@"%c%c%c%c",
                                     0 + arc4random_uniform(10),
                                     A + arc4random_uniform(26),
                                     0 + arc4random_uniform(10),
                                     A + arc4random_uniform(26)];
    
    JXItem * newItem = [[self alloc] initWithItemName:randomName
                                       valueInDollars:randomValue
                                         serialNumber:randomSerialNumber];
    
    return newItem;
}

- (NSString *)description {
    NSString * descriptionString = [NSString stringWithFormat:@"%@ (%@):Worth $%zd, recorded on %@",self.itemName,self.serialnumber,self.valueInDollars,self.createDate];
    return descriptionString;
}

- (instancetype)initWithItemName:(NSString *)name
                  valueInDollars:(NSInteger)value
                    serialNumber:(NSString *)sNumber {
    
    // 调用父类的指定初始化方法
    self = [super init];
    
    // 父类的指定初始化方法是否成功创建了对象
    if (self) {
        // 为实例变量设置初始值
        _itemName = name;
        _valueInDollars = value;
        _serialnumber = sNumber;
        
        // 设置_createDate为当前时间
        _createDate = [NSDate date];
        
        // 创建一个 NSUUID 对象
        NSUUID * uuid = [[NSUUID alloc] init];
        NSString * key = [uuid UUIDString];
        _itemKey = key;
    }
    
    // 返回初始化后的对象的新地址
    return self;
}


- (instancetype)initWithItemName:(NSString *)name {
    return [self initWithItemName:name valueInDollars:0 serialNumber:@""];
}

- (instancetype)init {
    return [self initWithItemName:@"Item"];
}

- (void)dealloc {
    NSLog(@"Destoryed:%@",self);
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:self.itemName forKey:@"itemName"];
    [aCoder encodeObject:self.serialnumber forKey:@"serialnumber"];
    [aCoder encodeObject:self.createDate forKey:@"createDate"];
    [aCoder encodeObject:self.itemKey forKey:@"itemKey"];
    [aCoder encodeInteger:self.valueInDollars forKey:@"valueInDollars"];
}
@end

  在这段代码中,凡是指向对象的指针都会用   encodeObject: forKey: 编码,而 self.valueInDollars 是用  encodeInteger: forKey: 进行编码的。

  为了能够编码 JXItem 对象,JXItem 的所有属性也必须遵守 NSCoding 协议。

  编码 JXItem 对象时,需要针对每个属性指定相应的键。当 JXHomepwner 从文件读取相应的数据并重新创建 JXItem 对象时,会根据键来设置属性。当应用需要根据编码后的数据初始化某个对象时,会向该对象发送 - (instancetype)initWithCoder:(NSCoder *)aDecoder 消息。消息应该还原之前通过  - (void)encodeWithCoder:(NSCoder *)aCoder 编码的所有对象,然后将这些对象赋值给相应的属性。

#import "JXItem.h"

@implementation JXItem

+ (instancetype)randomItem {
    // 创建不可变数组对象,包含三个形容词
    NSArray * randomAdjectiveList = @[
                                      @"Fluffy",
                                      @"Rusty",
                                      @"Shiny"
                                      ];
    // 创建不可变数组对象,包含三个名词
    NSArray * randomNounList = @[
                                 @"Bear",
                                 @"Spork",
                                 @"Mac"
                                 ];
    
    // 根据数组对象所含的对象的个数,得到随机索引
    // 注意:运算符%是模运算符,运算后得到的是余数
    NSInteger adjectiveIndex = arc4random() % randomAdjectiveList.count;
    NSInteger nounIndex = arc4random() % randomNounList.count;
    // 注意,类型为NSInteger 的变量不是对象
    NSString * randomName = [NSString stringWithFormat:@"%@ %@",randomAdjectiveList[adjectiveIndex],randomNounList[nounIndex]];
    
    NSInteger randomValue = arc4random_uniform(100);
    
    NSString * randomSerialNumber = [NSString stringWithFormat:@"%c%c%c%c",
                                     0 + arc4random_uniform(10),
                                     A + arc4random_uniform(26),
                                     0 + arc4random_uniform(10),
                                     A + arc4random_uniform(26)];
    
    JXItem * newItem = [[self alloc] initWithItemName:randomName
                                       valueInDollars:randomValue
                                         serialNumber:randomSerialNumber];
    
    return newItem;
}

- (NSString *)description {
    NSString * descriptionString = [NSString stringWithFormat:@"%@ (%@):Worth $%zd, recorded on %@",self.itemName,self.serialnumber,self.valueInDollars,self.createDate];
    return descriptionString;
}

- (instancetype)initWithItemName:(NSString *)name
                  valueInDollars:(NSInteger)value
                    serialNumber:(NSString *)sNumber {
    
    // 调用父类的指定初始化方法
    self = [super init];
    
    // 父类的指定初始化方法是否成功创建了对象
    if (self) {
        // 为实例变量设置初始值
        _itemName = name;
        _valueInDollars = value;
        _serialnumber = sNumber;
        
        // 设置_createDate为当前时间
        _createDate = [NSDate date];
        
        // 创建一个 NSUUID 对象
        NSUUID * uuid = [[NSUUID alloc] init];
        NSString * key = [uuid UUIDString];
        _itemKey = key;
    }
    
    // 返回初始化后的对象的新地址
    return self;
}


- (instancetype)initWithItemName:(NSString *)name {
    return [self initWithItemName:name valueInDollars:0 serialNumber:@""];
}

- (instancetype)init {
    return [self initWithItemName:@"Item"];
}

- (void)dealloc {
    NSLog(@"Destoryed:%@",self);
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:self.itemName forKey:@"itemName"];
    [aCoder encodeObject:self.serialnumber forKey:@"serialnumber"];
    [aCoder encodeObject:self.createDate forKey:@"createDate"];
    [aCoder encodeObject:self.itemKey forKey:@"itemKey"];
    [aCoder encodeInteger:self.valueInDollars forKey:@"valueInDollars"];
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super init];
    if (self) {
        _itemName = [aDecoder decodeObjectForKey:@"itemName"];
        _serialnumber = [aDecoder decodeObjectForKey:@"serialnumber"];
        _createDate = [aDecoder decodeObjectForKey:@"createDate"];
        _itemKey = [aDecoder decodeObjectForKey:@"itemKey"];
        _valueInDollars = [aDecoder decodeIntegerForKey:@"valueInDollars"];
    }
    return self;
}
@end

   - (instancetype)initWithCoder:(NSCoder *)aDecoder 也有一个类型为 NSCoder 的参数,和之前的 - (void)encodeWithCoder:(NSCoder *)aCoder 不同,该参数的作用是为初始化 JXItem 对象提供数据。这段代码通过向 NSCoder 对象发送 decodeObjectForKey: 重新设置相应的属性。

  • 应用沙盒

  每个iOS应用都有自己的专属的应用沙盒。应用沙盒就是文件系统中的目录,但是iOS系统会将每个应用沙盒目录与文件系统的其他备份隔离。应用沙盒包含以下多个目录:

应用程序包(application bundle) 包含应用可执行文件和所所有需要资源文件,例如NIB 文件和图像文件,它是一个只读目录
Documents/ 存放应用运行时生成的并且需要保留的数据。iTunes或iCloud会在同步设备时备份该目录。当设备发生故障的时候,可以从iTunes或iCloud恢复该目录中的文件。例如,JXHomepwner 应用可将用户所拥有的物品信息保存在Documents/中。
Library/Caches/ 存放应用运行时生成的需要保留的数据。与Documents/目录不同的是,iTunes或iCloud不会在同步设备时备份该目录。不备份缓存数据的主要原因是相关数据的体积可能很大,从而延长同步设备所需的时间。如果数据源是在别处(礼物:Web服务区),就可以将得到的数据保存在  Library/Caches/ 目录。当用户需要回复设备的时候,相关的应用只需要从数据源再次获取数据即可。
Library/Preferences/ 存放所有的偏好设置(Setting)应用也会在该目录中查找应用的设置信息,使用 NSUserDefault 类,可以通过  Library/Preferences/ 目录中的某个特定文件以键值对的形式保存数据。iTunes或iCloud会在同步设备时备份该目录
tmp/ 存放应用运行时所需要的临时数据。当某个应用还没有运行时,iOS系统可能回清除这个应用的  tmp/ 目录下的文件,但是为了节约用户设备空间,不能依赖这种自动清楚机制,而是当应用不再需要使用 tmp/ 目录中的文件时,就及时手动删除过这些文件。iTunes或iCloud不会在同步设备时备份 tmp/ 目录。通过 NSTemporarDirctory 函数可以得到应用沙盒中的  tmp/ 目录的全路径。
  • 获取文件路径

  下面为 JXHomepwner 增加保存和读取 JXItem 对象的功能,具体要求是:将所有的 JXItem 对象保存至 Documents 目录中的某个文件,并由 JXItemStore 对象负责该文件的写入与读取。为此, JXItemStore 对象需要获取响应文件的全路径。

#import "JXItemStore.h"
#import "JXItem.h"
#import "JXImageStore.h"

@interface JXItemStore ()

/** 可变数组,用来操作 JXItem 对象 */
@property (nonatomic,strong) NSMutableArray * privateItems;

@end

@implementation JXItemStore

- (NSString *)itemArchivePath {
    // 注意第一个参数是 NSDocumentDirectory 而不是 NSDocumentationDirectory
    NSArray * documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    
    // 从 documentDirectories 数组获取第一个,也是唯一一个文档目录路径
    NSString * documentDirectory = [documentDirectories firstObject];
    
    return [documentDirectory stringByAppendingPathComponent:@"items.archive"];
}
// 单粒对象
+ (instancetype)sharedStore {
    static JXItemStore * sharedStore = nil;
    
    // 判断是否需要创建一个 sharedStore 对象
    if (!sharedStore) {
        sharedStore = [[self alloc] init];
    }
    return sharedStore;
}
- (NSArray *)allItem {
    return [self.privateItems copy];
}

- (JXItem *)createItem {
    JXItem * item = [JXItem randomItem];
    [self.privateItems addObject:item];
    return item;
}


/**
 *  还可以调用 [self.privateItems removeObject:item]
 *  [self.privateItems removeObjectIdenticalTo:item] 与上面的方法的区别就是:上面的方法会枚举数组,向每一个数组发送 isEqual: 消息。
 *  isEqual: 的作用是判断当前对象和传入对象所包含的数据是否相等。可能会复写 这个方法。
 *  removeObjectIdenticalTo: 方法不会比较对象所包含的数据,只会比较指向对象的指针
 *
 *  @param item 需要删除的对象
 */
- (void)removeItem:(JXItem *)item {
    
    [self.privateItems removeObjectIdenticalTo:item];
    
    [[JXImageStore sharedStore] deleteImageForKey:item.itemKey];
}


- (void)moveItemAtIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex {
    
    // 如果起始位置和最终位置相同,则不懂
    if (fromIndex == toIndex) return;
    
    // 需要移动的对象的指针
    JXItem * item = self.privateItems[fromIndex];
    
    // 将 item 从 allItem 数组中移除
    [self.privateItems removeObjectAtIndex:fromIndex];
    
    // 根据新的索引位置,将item 插入到allItem 数组中
    [self.privateItems insertObject:item atIndex:toIndex];
}

#pragma mark - 懒加载
- (NSMutableArray *)privateItems{
    if (_privateItems == nil) {
        _privateItems = [[NSMutableArray alloc] init];
    }
    return _privateItems;
}
@end

  通过C函数  NSSearchPathForDirectoriesInDomains 可以得到沙盒总的某种目录的全路径。该函数有三个实参,其中后两个实参需要传入固定值。第一个实参是  NSSearchPathDirectory 类型的常量,负责制定目录的类型。例如,传入 NSCachesDirectory 可以得到沙盒中的  Caches 目录的路径。

  将某个 NSSearchPathDirectory 常量(例如 NSDocumentDirectory )作为关键词查找文档,就能找到其他的常量。

   NSSearchPathForDirectoriesInDomains 函数的返回值是 NSArray 对象,包含的都是 NSString 对象。为什么该函数的返回值不是一个 NSString 对象?这是因为对 Mac OS X,可能会有多个目录匹配某组指定的查询条件。但是在iOS上,一种目录类型只会有一个匹配的目录。所以上面的这段代码会获取数组的第一个也是唯一一个 NSString 对象,然后在该字符穿的后面追加固化文件的文件名,并最终得到保存 JXItem 对象的文件路径。

  •  NSKeyedArchiver 与 NSKeyedUnarchiver 

  之前我们修改了 JXItem 类,能使 JXHomepwner 固化 JXItem 。 此外我们还要解决的两个问题是:1. 如何保存或者读取数据? 2. 何时保存或者读取数据? 先解决 保存 问题。应用应该在退出时,通过  NSKeyedArchiver  类保存 JXItem 对象。

#import <Foundation/Foundation.h>

@class JXItem;
@interface JXItemStore : NSObject

/** 存放 JXItem 对象数组 */
@property (nonatomic,readonly) NSArray * allItem;

// 注意,这是一个类方法,前缀是+
+ (instancetype)sharedStore;

- (JXItem *)createItem;

/**
 *  删除对象
 *
 *  @param item 需要删除的对象
 */
- (void)removeItem:(JXItem *)item;

/**
 *  移动对象
 *
 *  @param fromIndex 移动对象的起始位置
 *  @param toIndex   移动后的位置
 */
- (void)moveItemAtIndex:(NSInteger)fromIndex
                toIndex:(NSInteger)toIndex;

- (BOOL)saveChanges;

@end
#import "JXItemStore.h"
#import "JXItem.h"
#import "JXImageStore.h"

@interface JXItemStore ()

/** 可变数组,用来操作 JXItem 对象 */
@property (nonatomic,strong) NSMutableArray * privateItems;

@end

@implementation JXItemStore

- (BOOL)saveChanges {
    NSString * path = [self itemArchivePath];
    
    // 如果固化成功就返回YES
    return [NSKeyedArchiver archiveRootObject:self.privateItems toFile:path];
}

- (NSString *)itemArchivePath {
    // 注意第一个参数是 NSDocumentDirectory 而不是 NSDocumentationDirectory
    NSArray * documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    
    // 从 documentDirectories 数组获取第一个,也是唯一一个文档目录路径
    NSString * documentDirectory = [documentDirectories firstObject];
    
    return [documentDirectory stringByAppendingPathComponent:@"items.archive"];
}
// 单粒对象
+ (instancetype)sharedStore {
    static JXItemStore * sharedStore = nil;
    
    // 判断是否需要创建一个 sharedStore 对象
    if (!sharedStore) {
        sharedStore = [[self alloc] init];
    }
    return sharedStore;
}
- (NSArray *)allItem {
    return [self.privateItems copy];
}

- (JXItem *)createItem {
    JXItem * item = [JXItem randomItem];
    [self.privateItems addObject:item];
    return item;
}


/**
 *  还可以调用 [self.privateItems removeObject:item]
 *  [self.privateItems removeObjectIdenticalTo:item] 与上面的方法的区别就是:上面的方法会枚举数组,向每一个数组发送 isEqual: 消息。
 *  isEqual: 的作用是判断当前对象和传入对象所包含的数据是否相等。可能会复写 这个方法。
 *  removeObjectIdenticalTo: 方法不会比较对象所包含的数据,只会比较指向对象的指针
 *
 *  @param item 需要删除的对象
 */
- (void)removeItem:(JXItem *)item {
    
    [self.privateItems removeObjectIdenticalTo:item];
    
    [[JXImageStore sharedStore] deleteImageForKey:item.itemKey];
}


- (void)moveItemAtIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex {
    
    // 如果起始位置和最终位置相同,则不懂
    if (fromIndex == toIndex) return;
    
    // 需要移动的对象的指针
    JXItem * item = self.privateItems[fromIndex];
    
    // 将 item 从 allItem 数组中移除
    [self.privateItems removeObjectAtIndex:fromIndex];
    
    // 根据新的索引位置,将item 插入到allItem 数组中
    [self.privateItems insertObject:item atIndex:toIndex];
}

#pragma mark - 懒加载
- (NSMutableArray *)privateItems{
    if (_privateItems == nil) {
        _privateItems = [[NSMutableArray alloc] init];
    }
    return _privateItems;
}
@end

  这段代码中的  archiveRootObject: toFile: 会将  privateItems  中的所有 JXItem 对象都保存至路径为  itemArchivePath  文件。工作原理:

  1.  archiveRootObject: toFile: 会首先创建一个  NSKeyedArchiver 对象。( NSKeyedArchiver 是抽象类 NSCoder 的具体实现子类)。
  2. 然后, archiveRootObject: toFile: 会向  privateItems 发送  encodeWithCoder: 消息,并传入  NSKeyedArchiver 对象作为第一个参数。
  3.  privateItems 的  encodeWithCoder: 方法会向其包含的所有 JXItem 对象发送  encodeWithCoder: 消息,并传入同一个 NSKeyedArchiver 对象。这些 JXItem 对象都会将其属性编码至同一个  NSKeyedArchiver 对象。
  4. 当所有的对象都完成编码之后, NSKeyedArchiver 对象就会将数据写入指定的文件。
#import "AppDelegate.h"
#import "JXItemsViewController.h"
#import "JXItemStore.h"
@interface AppDelegate ()

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    
    // 添加初始化代码
    // 创建 JXItemsViewController 对象
    JXItemsViewController * itemsViewController = [[JXItemsViewController alloc] init];
    
    // 将 JXItemsViewController 的标示图加入窗口
    self.window.rootViewController = itemsViewController;
    
    // 将 UINavigationController 对象设置为 UIWindow 对象的根视图控制器。
    // 这样就可以将 UINavigationController 对象的视图添加到屏幕中
    UINavigationController * navController = [[UINavigationController alloc] initWithRootViewController:itemsViewController];
    self.window.rootViewController = navController;
    
    self.window.backgroundColor = [UIColor whiteColor];
    
    [self.window makeKeyAndVisible];
    return YES;
}


- (void)applicationWillResignActive:(UIApplication *)application {
    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}


- (void)applicationDidEnterBackground:(UIApplication *)application {
   
    BOOL success = [[JXItemStore sharedStore] saveChanges];
    if (success) {
        NSLog(@"Saved");
    } else {
        NSLog(@"Faild");
    }
}


- (void)applicationWillEnterForeground:(UIApplication *)application {
    // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}


- (void)applicationDidBecomeActive:(UIApplication *)application {
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}


- (void)applicationWillTerminate:(UIApplication *)application {
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}


@end
#import "JXItemStore.h"
#import "JXItem.h"
#import "JXImageStore.h"

@interface JXItemStore ()

/** 可变数组,用来操作 JXItem 对象 */
@property (nonatomic,strong) NSMutableArray * privateItems;

@end

@implementation JXItemStore

- (BOOL)saveChanges {
    NSString * path = [self itemArchivePath];
    
    // 如果固化成功就返回YES
    return [NSKeyedArchiver archiveRootObject:self.privateItems toFile:path];
}

- (NSString *)itemArchivePath {
    // 注意第一个参数是 NSDocumentDirectory 而不是 NSDocumentationDirectory
    NSArray * documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    
    // 从 documentDirectories 数组获取第一个,也是唯一一个文档目录路径
    NSString * documentDirectory = [documentDirectories firstObject];
    
    return [documentDirectory stringByAppendingPathComponent:@"items.archive"];
}
// 单粒对象
+ (instancetype)sharedStore {
    static JXItemStore * sharedStore = nil;
    
    // 判断是否需要创建一个 sharedStore 对象
    if (!sharedStore) {
        sharedStore = [[self alloc] init];
    }
    return sharedStore;
}
- (NSArray *)allItem {
    return [self.privateItems copy];
}

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


/**
 *  还可以调用 [self.privateItems removeObject:item]
 *  [self.privateItems removeObjectIdenticalTo:item] 与上面的方法的区别就是:上面的方法会枚举数组,向每一个数组发送 isEqual: 消息。
 *  isEqual: 的作用是判断当前对象和传入对象所包含的数据是否相等。可能会复写 这个方法。
 *  removeObjectIdenticalTo: 方法不会比较对象所包含的数据,只会比较指向对象的指针
 *
 *  @param item 需要删除的对象
 */
- (void)removeItem:(JXItem *)item {
    
    [self.privateItems removeObjectIdenticalTo:item];
    
    [[JXImageStore sharedStore] deleteImageForKey:item.itemKey];
}


- (void)moveItemAtIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex {
    
    // 如果起始位置和最终位置相同,则不懂
    if (fromIndex == toIndex) return;
    
    // 需要移动的对象的指针
    JXItem * item = self.privateItems[fromIndex];
    
    // 将 item 从 allItem 数组中移除
    [self.privateItems removeObjectAtIndex:fromIndex];
    
    // 根据新的索引位置,将item 插入到allItem 数组中
    [self.privateItems insertObject:item atIndex:toIndex];
}

#pragma mark - 懒加载
- (NSMutableArray *)privateItems{
    
    NSString * path = [self itemArchivePath];
    _privateItems = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
    
    // 如果没有就创建
    if (_privateItems == nil) {
        _privateItems = [[NSMutableArray alloc] init];
    }
    return _privateItems;
}
@end

  这段代码中的  unarchiveObjectWithFile: 类方法会创建一个  NSKeyedUnarchiver 对象,然后根据指定的路径载入固化文件。接着, NSKeyedUnarchiver 类会查看固化文件中的跟对象,然后根据对象的类型创建相应的对象。固化的时候是保存的什么对象,那么解固的时候就会返回什么对象。

  

iOS 保存、读取与应用状态