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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...