iOS 保存、讀取與應用狀態

来源:http://www.cnblogs.com/wang-com/archive/2016/10/24/5979285.html
-Advertisement-
Play Games

固化 對於大多數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 類會查看固化文件中的跟對象,然後根據對象的類型創建相應的對象。固化的時候是保存的什麼對象,那麼解固的時候就會返回什麼對象。

  


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在學校開課學習了android的一些簡單的UI組件,佈局,四大組件學習了2個,數據存儲及網路通信,都是一些簡單的概念,入門而已。許多東西需要自己去學習。 學習一下 Android開發環境的搭建,兩種方式開發:一種是Eclipse,另一種是Android Studio。 Eclipse 一、下載and ...
  • 1. JSONObject對象的optXXX和getXXX的區別? getInt("key") 取值 不存在 或者類型不對 報錯optInt("key") 取值 不存在 返回預設值 getDouble("key") 取值 不存在 或者類型不對 報錯optDouble("key",0) 取值 不存在 ...
  • 在Android 4.4系統中,外置存儲卡(SD卡)被稱為二級外部存儲設備(secondary storage),應用程式已無法往外置存儲卡(SD卡)寫入數據,並且WRITE_EXTERNAL_STORAGE只為設備上的主要外部存儲(primary storage)授予寫許可權,對於其他外部存儲,其上 ...
  • 滿網都是微信小程式,技術dog們不關註都不行了。先別忙著去學怎麼開發小程式,先糾正一下你對微信小程式的三觀吧~~~~ 小程式目前被炒得沸沸揚揚,無數媒體和企業藉機獲取閱讀流量。 這再次證明一點,微信想讓什麼火,真的就能讓什麼火。 先列出8個多數人都搞錯的問題: 以上8個是很多人憑直覺得出的結論,但真 ...
  • 關於Retrofit+OkHttp的強大這裡就不多說了,還沒瞭解的同學可以自行去百度。這篇文章主要講如何利用Retrofit+OkHttp來實現一個較為簡單的緩存策略:即有網環境下我們請求數據時,如果沒有緩存或者緩存過期了,就去伺服器拿數據,並且將新緩存保存下來,如果有緩存而且沒有過期,則直接使用緩 ...
  • 想要瞭解Android新版本的的新特性,從頭開始吧,這是Android3.0新加入的widget,以前也接觸過,但是沒有好好的研究過,今天寫了一個小程式,研究一下ViewPager。 這個程式是支持左右滑動的View,核心是ViewPager。講解都在註釋中進行。 代碼如下: MainActivit ...
  • Android 7.1 預覽版發佈, 其中App Shortcuts是新提供的一種快捷訪問方式, 形式為長按應用圖標出現的長條, 本文介紹其用法. ...
  • 目錄 1、Realm簡介 2、環境配置 3、在Application中初始化Realm 4、創建實體 5、增刪改查 6、非同步操作 7、Demo地址(https://github.com/RaphetS/DemoRealm ) ![ ](http://upload images.jianshu.io/ ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...