固化 對於大多數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 文件。工作原理:
- archiveRootObject: toFile: 會首先創建一個 NSKeyedArchiver 對象。( NSKeyedArchiver 是抽象類 NSCoder 的具體實現子類)。
- 然後, archiveRootObject: toFile: 會向 privateItems 發送 encodeWithCoder: 消息,並傳入 NSKeyedArchiver 對象作為第一個參數。
- privateItems 的 encodeWithCoder: 方法會向其包含的所有 JXItem 對象發送 encodeWithCoder: 消息,並傳入同一個 NSKeyedArchiver 對象。這些 JXItem 對象都會將其屬性編碼至同一個 NSKeyedArchiver 對象。
- 當所有的對象都完成編碼之後, 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 類會查看固化文件中的跟對象,然後根據對象的類型創建相應的對象。固化的時候是保存的什麼對象,那麼解固的時候就會返回什麼對象。