在上一篇博客中,咱們介紹了用plist文件進行數據持久化的方法。雖然簡單易用,但隨着開發的深刻,你會發現,這種方式仍是有很大的侷限性。試想,若是咱們能夠將用戶的登陸返回信息模型,遊戲中角色的屬性信息模型進行直接的持久化存取,那是否是很是爽的事,幸運的是,咱們能夠經過歸檔,來設計一個這樣的數據模型。git
歸檔也是iOS提供給開發者的一種數據存儲的方式,事實上,幾乎全部的數據類型均可以經過歸檔來進行存取。其存儲與讀取的過程,主要封裝在兩個類中:NSKeyedArchiver和NSKeyedUnarchiver。github
歸檔是將一種或者多種數據類型進行序列化,解歸檔的過程就是將序列化的數據進行反序列化的解碼,這裏須要注意一點,歸檔的核心並不是是數據的持久化處理,而是數據的序列化處理,持久化的處理依然是經過文件存取來實現的。所以,被歸檔的數據類型都必須遵照一個相同的協議,才能在這個協議的約束下進行正確的歸檔與解歸檔,這個協議就是NSCoding協議,咱們能夠先來看一下NSCoding中的內容:數組
@protocol NSCoding - (void)encodeWithCoder:(NSCoder *)aCoder; - (id)initWithCoder:(NSCoder *)aDecoder; @end
這個協議很是簡單,一個init的歸檔方法,一個encode的解歸檔方法,NSCoder就是歸檔對象。原則上說,不管是什麼數據類型的對象,系統的或者是咱們自定義的,均可以經過實現這個協議中的方法來支持歸檔操做。安全
這種方式,我我的理解,很相似於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]);
上面的示例是對字符串類型進行的歸檔,是對單一的數據對象進行的歸檔,固然,這裏的對象是支持數組、字典等集合的,但集合其中的對象,也必須所有支持歸檔操做。優化
除了上面的類方法,咱們還能夠本身構造一個歸檔對象,來對多種不一樣的對象進行歸檔: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
上面介紹中有提到,原則上,任何遵照了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吧。
經過上面對歸檔的介紹,咱們能夠發現歸檔一個十分有潛力的應用:能夠自由存取自定義的數據對象。這個特性的優點是毫無疑問的,除了可使咱們的數據用起來更加方便,無需屢次解析數據外,安全性也更好。可是也帶來了一個缺陷,每一個類都須要實現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繼承於這個基類,無需其餘處理直接支持歸檔,修改與優化都不受影響。
這個model集成在了個人一個開源的開發框架中,固然,那裏面也綜合和許多許多這樣方便開發者使用的功能,若是你感興趣,能夠在https://github.com/ZYHshao/YHBaseFoundationTest上面看到。若是你發現了一些bug或者能夠添加或者優化的地方,請務必告知我,十分你感謝。QQ:316045346
專一技術,熱愛生活,交流技術,也作朋友。
——琿少 QQ羣:203317592