iOS數據持久化之二——歸檔與設計可存儲化的數據模型基類

iOS數據持久化之二——歸檔與設計可存儲化的數據模型基類

1、引言

        在上一篇博客中,咱們介紹了用plist文件進行數據持久化的方法。雖然簡單易用,但隨着開發的深刻,你會發現,這種方式仍是有很大的侷限性。試想,若是咱們能夠將用戶的登陸返回信息模型,遊戲中角色的屬性信息模型進行直接的持久化存取,那是否是很是爽的事,幸運的是,咱們能夠經過歸檔,來設計一個這樣的數據模型。git

2、先來精通歸檔吧

        歸檔也是iOS提供給開發者的一種數據存儲的方式,事實上,幾乎全部的數據類型均可以經過歸檔來進行存取。其存儲與讀取的過程,主要封裝在兩個類中:NSKeyedArchiver和NSKeyedUnarchiver。github

一、歸檔的原理

        歸檔是將一種或者多種數據類型進行序列化,解歸檔的過程就是將序列化的數據進行反序列化的解碼,這裏須要注意一點,歸檔的核心並不是是數據的持久化處理,而是數據的序列化處理,持久化的處理依然是經過文件存取來實現的。所以,被歸檔的數據類型都必須遵照一個相同的協議,才能在這個協議的約束下進行正確的歸檔與解歸檔,這個協議就是NSCoding協議,咱們能夠先來看一下NSCoding中的內容:數組

@protocol NSCoding

- (void)encodeWithCoder:(NSCoder *)aCoder;
- (id)initWithCoder:(NSCoder *)aDecoder;

@end

 

這個協議很是簡單,一個init的歸檔方法,一個encode的解歸檔方法,NSCoder就是歸檔對象。原則上說,不管是什麼數據類型的對象,系統的或者是咱們自定義的,均可以經過實現這個協議中的方法來支持歸檔操做。安全

二、幾種歸檔與解歸檔的應用

(1)經過類方法來對rootKey進行歸檔

        這種方式,我我的理解,很相似於NSUserDefaults中的standardUserDefaults,只是後者是系統爲咱們建立的一個默認plist文件,而rootKey是系統爲咱們建立的一個默認的歸檔鍵值。提及來比較複雜,舉個例子就十分清晰了:框架

    NSString *homeDictionary = NSHomeDirectory();//獲取根目錄
    NSString *homePath  = [homeDictionary stringByAppendingPathComponent:@"atany.archiver"];//添加儲存的文件名
    //方式一:經過data數據歸檔,在將數據寫入文件
    NSData *data= [NSKeyedArchiver archivedDataWithRootObject:@"123"];
    [data writeToFile:homePath atomically:YES];
    //方式二:直接寫入文件
    [NSKeyedArchiver archiveRootObject:@"456" toFile:homePath];
    //方式一和方式二的效果徹底同樣 只是解歸檔的時候不一樣
    
    //方式一的解歸檔:先獲取data數據,在進行data數據的解歸檔
    NSLog(@"%@",[NSKeyedUnarchiver unarchiveObjectWithData:data]);
    //方式二的解歸檔:直接解文件中的歸檔
    NSLog(@"%@",[NSKeyedUnarchiver unarchiveObjectWithFile:homePath]);

 

上面的示例是對字符串類型進行的歸檔,是對單一的數據對象進行的歸檔,固然,這裏的對象是支持數組、字典等集合的,但集合其中的對象,也必須所有支持歸檔操做。優化

(2)經過構造新的archiver對象,對多個對象進行歸檔

        除了上面的類方法,咱們還能夠本身構造一個歸檔對象,來對多種不一樣的對象進行歸檔:atom

    NSString *homeDictionary = NSHomeDirectory();//獲取根目錄
    NSString *homePath  = [homeDictionary stringByAppendingPathComponent:@"atany.archiver"];//添加儲存的文件名
    //這裏建立一個可變的data對象做爲歸檔的容器
    NSMutableData * data = [[NSMutableData alloc]init];
    //建立一個歸檔對象,歸檔後寫入data數據
    NSKeyedArchiver * archiver = [[NSKeyedArchiver alloc]initForWritingWithMutableData:data];
    //對下面的字符串和int值進行歸檔序列化
    [archiver encodeObject:@"jaki" forKey:@"name"];
    [archiver encodeInt:24 forKey:@"age"];
    //寫入data
    [archiver finishEncoding];
    //寫入文件
    [data writeToFile:homePath atomically:YES];
    
    //建立解歸檔的反序列化對象
    NSKeyedUnarchiver * unarchiver = [[NSKeyedUnarchiver alloc]initForReadingWithData:data];
    //進行反序列化
    NSString * name = [unarchiver decodeObjectForKey:@"name"];
    int age = [unarchiver decodeIntForKey:@"age"];
    //打印信息
    NSLog(@"\nname:%@\nage:%d",name,age);

 

結果以下:spa

(3)進行自定義對象的歸檔

        上面介紹中有提到,原則上,任何遵照了NSCoding協議的類均可以進行歸檔操做,那麼對於咱們自定義的對象,咱們該如何來作呢?設計

首先,咱們新建一個類:指針

仿照上面的例子,咱們寫一個這樣的類:

@interface MyObject : NSObject
@property(nonatomic,strong)NSString * name;
@property(nonatomic,assign)int age;
@end

 

對其進行歸檔:

    //進行歸檔
    MyObject * obj = [[MyObject alloc]init];
    obj.name = @"jaki";
    obj.age = 24;
    NSData * data =  [NSKeyedArchiver archivedDataWithRootObject:obj];
    //進行解檔
    MyObject * obj2 = [NSKeyedUnarchiver unarchiveObjectWithData:data];
    NSLog(@"\nname:%@\nage:%d",obj2.name,obj2.age);

 

直接運行,程序會崩潰掉,打印以下:

能夠看出,正是咱們前邊說過的,必須遵照歸檔協議的對象,才能夠被歸檔,咱們在MyObject類中實現以下兩個方法:

//解檔方法
- (instancetype)initWithCoder:(NSCoder *)coder
{
    if (self=[super init]) {
        _name = [coder decodeObjectForKey:@"name"];
        _age = [coder decodeIntForKey:@"age"];
    }
    return self;
}

//歸檔方法
- (void)encodeWithCoder:(NSCoder *)coder
{
    [coder encodeObject:_name forKey:@"name"];
    [coder encodeInt:_age forKey:@"age"];
}

 

添加了上面兩個方法,咱們自定義的對象就能夠自由歸檔存取,並能夠寫入本地,很是cool吧。

3、設計能夠歸檔存取的數據模型基類

一、動機與初衷

        經過上面對歸檔的介紹,咱們能夠發現歸檔一個十分有潛力的應用:能夠自由存取自定義的數據對象。這個特性的優點是毫無疑問的,除了可使咱們的數據用起來更加方便,無需屢次解析數據外,安全性也更好。可是也帶來了一個缺陷,每一個類都須要實現NSCoding中的兩個方法是十分繁瑣的,而且類越複雜,這個步驟越繁瑣,若是在以後的修改和優化中類作了改變,相應的方法也要作改變,這將增長很大的工做量而且埋下潛在bug的風險。

        因此咱們會想,可否設計一個這樣的model基類,來使須要存儲的model都繼承於它,使咱們的model不須要實現NSCoding方法的同時能夠支持歸檔呢,經過runtime和OC語言特性的一些小技巧,咱們是能夠作到的。

二、基類模型的設計

        咱們新建一個BaseModel類,核心方法以下:

//歸檔與解歸檔的方法
- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super init];
    if (self) {
        //獲取全部屬性
        NSArray * porpertyArray = [self getAllPropertys];
        for (NSString * name in porpertyArray) {
            //去掉屬性名前面的_
            NSString * key = [name substringFromIndex:1];
            //約定好的鍵值對 c+key
            [self setValue:[coder decodeObjectForKey:[NSString stringWithFormat:@"c%@",key]] forKey:key];
        }
    }
    return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
    
    //獲取全部屬性
    NSArray * porpertyArray = [self getAllPropertys];
    for (NSString * name in porpertyArray) {
        //去掉屬性名前面的_
        NSString * key = [name substringFromIndex:1];
        //約定好的鍵值對 c+key
        [coder encodeObject:[self valueForKey:key] forKey:[NSString stringWithFormat:@"c%@",key]];
    }
}
//獲取model全部屬性
-(NSArray *)getAllPropertys{
    NSMutableArray * array = [[NSMutableArray alloc]init];
    
    unsigned int * count = malloc(sizeof(unsigned int));
    //調用runtime的方法
    //Ivar:方法返回的對象內容對象,這裏將返回一個Ivar類型的指針
    //class_copyIvarList方法能夠捕獲到類的全部變量,將變量的數量存在一個unsigned int的指針中
    Ivar * mem = class_copyIvarList([self class], count);
    //進行遍歷
    for (int i=0; i< *count ; i++) {
        //經過移動指針進行遍歷
        Ivar var = * (mem+i);
        //獲取變量的名稱
        const char * name = ivar_getName(var);
        NSString * str = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
        [array addObject:str];
    }
    //釋放內存
    free(count);
    //注意處理野指針
    count=nil;
    return array;
}

 

經過這樣的一個runtime機制,咱們能夠很方便的是新建的model繼承於這個基類,無需其餘處理直接支持歸檔,修改與優化都不受影響。

4、爲志同道合的朋友分享

        這個model集成在了個人一個開源的開發框架中,固然,那裏面也綜合和許多許多這樣方便開發者使用的功能,若是你感興趣,能夠在https://github.com/ZYHshao/YHBaseFoundationTest上面看到。若是你發現了一些bug或者能夠添加或者優化的地方,請務必告知我,十分你感謝。QQ:316045346

 

專一技術,熱愛生活,交流技術,也作朋友。

——琿少 QQ羣:203317592

相關文章
相關標籤/搜索