前言
移動端的資料持久化儲存方法有很多種:fmdb、coredata、realm(很強大)、archive(歸檔)等等… 眾所周知,sql系列只能儲存基本資料型別,不支援直接儲存Object物件型別。realm雖然支援儲存Object物件,但是對於已經在使用sql系列的專案來說替換成本還是挺高的。
所以我覺得當你使用sql型別來儲存資料但是又想儲存少量的Object物件,或者App僅僅需要少量的資料儲存的時候是完全可以使用歸檔來滿足你的需求的。
所以我封裝了WZXArchiver來幫我無腦歸、解檔。順便分享我是怎麼封裝的,如果你有更好的做法或者意見歡迎評論!。
WZXArchiver 已實現功能:
1. 基本資料的自動歸、解檔。
2. 包含物件的物件的自動歸、解檔(解檔有點不完美)。
3. 清除所有的歸檔。(這裡不講)
正文
要封裝一個自動歸檔、解檔類,首先我們知道歸檔的基本使用.
歸檔的基本使用
建立一個NSObject物件: ManModel,它有兩個成員變數NSString型別的name、NSInteger型別的age。
1 2 3 4 5 6 |
@interface ManModel : NSObject @property(nonatomic,copy)NSString * name; @property(nonatomic,assign)NSInteger age; @end |
在.m中實現的協議方法(**所有原生的類都是實現了NSCoding協議的,所以你不用在@interface ManModel : NSObject
後面加上**)
1 2 3 4 5 6 7 8 9 10 11 12 |
- (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.name forKey:@"name"]; [aCoder encodeInteger:self.age forKey:@"age"]; } - (id)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { self.name = [aDecoder decodeObjectForKey:kNameKey]; self.age = [aDecoder decodeIntegerForKey:kAgeKey]; } return self; } |
然後就可以開始歸檔了:
1 2 3 4 5 |
//歸檔 [NSKeyedArchiver archiveRootObject:你要歸檔的物件 toFile:@"地址"]; //解檔 [NSKeyedUnarchiver unarchiveObjectWithFile:@"地址"]; |
如何封裝一個自動歸檔、解檔類
首先肯定是建立一個NSObject的類別
為什麼用類別又不是建立一個工具類?
1. 因為用類別可以少很多引數。
2. 可以重寫的協議方法來實現自動歸、解檔。
新增方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
/** * 通過自定的名字歸檔 * * @param name 名字 * * @return 是否成功 */ - (BOOL)wzx_archiveToName:(NSString *)na ![Uploading F43FE3F2-826B-4524-B7B6-ABE3CA3D52AD_354525.png . . .]me; /** * 通過之前歸檔的名字解檔 * * @param name 名字 * * @return 解檔的物件 */ + (id)wzx_unArchiveToName:(NSString *)name; |
這裡一定要傳入一個算是識別符號的字串,因為我歸檔的時候要以它為檔名WZX_類名_識別符號.archiver
,解檔的時候再根據這個字串去解檔。
獲取物件的成員變數
要實現自動xxx,基本上是要用到runtime的,這裡也不例外。
不過這裡有一點要注意一下:我把獲取物件成員變數的方法單獨放在一個類別裡,提高複用性。
獲取物件的成員變數的流程:
1. 建立類別#import "NSObject+WZXProperties.h"
2. 匯入標頭檔案 #import
3. 獲取成員變數
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
- (NSArray *)wzx_allProperty { NSMutableArray * propertyArr = [NSMutableArray array]; unsigned int outCount; objc_property_t * properties = class_copyPropertyList([self class], &outCount); for (int i = 0; i outCount; i++) { objc_property_t property = properties[i]; NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding] ; NSString * propertyType = [[NSString alloc] initWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding] ; [propertyArr addObject:@{ @"name":propertyName, @"type":[self judgementType:propertyType] }]; } free(properties); return propertyArr; } |
這裡可以看到,我返回的是一個字典陣列,每個字典包含name(變數名)和type(變數型別)。變數型別肯定是要根據attributes來判斷的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
- (NSString *)judgementType:(NSString *)attributes { if ([attributes hasPrefix:@"T"]) { if ([attributes hasPrefix:@"T@"]) { if ([attributes hasPrefix:@"T@"NSString""]) { return @"NSString"; } else if([attributes hasPrefix:@"T@"NSDictionary""]) { return @"NSDictionary"; } else if([attributes hasPrefix:@"T@"NSArray""]) { return @"NSArray"; } else if([attributes hasPrefix:@"T@"NSMutableString""]) { return @"NSMutableString"; } else if([attributes hasPrefix:@"T@"NSMutableDictionary""]) { return @"NSMutableDictionary"; } else if([attributes hasPrefix:@"T@"NSMutableArray""]) { return @"NSMutableArray"; } else if([attributes hasPrefix:@"T@"NSData""]) { return @"NSData"; } else if([attributes hasPrefix:@"T@"NSMutableData""]) { return @"NSMutableData"; } else if([attributes hasPrefix:@"T@"NSSet""]) { return @"NSSet"; } else if([attributes hasPrefix:@"T@"NSMutableSet""]) { return @"NSMutableSet"; } else { return [NSString stringWithFormat:@"__Model__:%@",[attributes componentsSeparatedByString:@"""][1]]; } } if ([attributes hasPrefix:@"Tq"]) { return @"NSInteger"; } else if ([attributes hasPrefix:@"TQ"]) { return @"NSUInteger"; } else if ([attributes hasPrefix:@"Td"]) { return @"double"; } else if ([attributes hasPrefix:@"TB"]) { return @"BOOL"; } else if ([attributes hasPrefix:@"Ti"]) { return @"int"; } else if ([attributes hasPrefix:@"Tf"]) { return @"float"; } else if ([attributes hasPrefix:@"Ts"]) { return @"short"; } return @""; } else { //錯誤的字串 return @""; } } |
裡面有一個__Model__:
,用來判斷包含的自定義成員變數,傳回"__Model__:自定義成員變數的類名"
。
自動化歸檔
重寫encodeWithCoder:方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
- (void)encodeWithCoder:(NSCoder *)aCoder { NSArray * propertyArr = [self wzx_allProperty]; for (NSDictionary * propertyDic in propertyArr) { [self encodeWithType:propertyDic[@"type"] Name:propertyDic[@"name"] Coder:aCoder]; } } - (void)encodeWithType:(NSString *)type Name:(NSString *)name Coder:(NSCoder *)aCoder { if ([self isObject:type]) { [aCoder encodeObject:[self valueForKey:name] forKey:name]; } else if([type isEqualToString:@"BOOL"]){ [aCoder encodeBool:[[self valueForKey:name] boolValue] forKey:name]; } else if([type isEqualToString:@"float"]){ [aCoder encodeFloat:[[self valueForKey:name] floatValue] forKey:name]; } else if([type isEqualToString:@"double"]){ [aCoder encodeFloat:[[self valueForKey:name] doubleValue] forKey:name]; } else if([type isEqualToString:@"int"]|| [type isEqualToString:@"short"]){ [aCoder encodeInt:[[self valueForKey:name] intValue] forKey:name]; } else if([type isEqualToString:@"NSInteger"]|| [type isEqualToString:@"NSUInteger"]){ [aCoder encodeInteger:[[self valueForKey:name] integerValue] forKey:name]; } if ([type hasPrefix:@"__Model__:"]) { NSString * className = [type componentsSeparatedByString:@"__Model__:"][1]; NSString * path = [NSString stringWithFormat:@"%@_%@_%@_%@",NSStringFromClass(self.class),self.WZX_Archiver_Name,className,name]; [[self valueForKey:name] wzx_archiveToName:path andIsSon:YES]; } } - (BOOL)isObject:(NSString *)type { NSArray * objectTypeArr = @[@"NSString", @"NSMutableString", @"NSArray", @"NSMutableArray", @"NSDictionary", @"NSMutableDictionary", @"NSData", @"NSMutableData", @"NSSet", @"NSMutableSet"]; return [objectTypeArr containsObject:type]; } |
這裡基本就是判斷型別來分別編碼,就幾個點要說一下:
1. 通過[self valueForKey:xxx]
可以取出self的成員變數對應的值。
2. 通過是否以__Model__ :
開頭來判斷是不是自定義的物件,若是自定義的物件再通過wzx_archiveToName: andIsSon:
來將包含的子物件歸檔,子物件歸檔的地址是這樣的:WZX_物件類名_物件定義的識別符號_子物件類名_子物件名.archiver
。
仔細想想,這裡物件定義的識別符號其實我們是得不到的,因為這是在協議的方法裡面,無法直接傳遞name到這個方法中,所以我們要給這個類別新增一個成員變數。
給類別新增成員變數
首先若你希望物件能獲取這個成員變數,你可以直接在.h中新增一個成員變數。 否則你需要在.m中新增:
1 2 3 |
@interface NSObject () @property(nonatomic, copy)NSString * WZX_Archiver_Name; @end |
然後重寫set、get方法,在類別中新增成員變數還是得用到runtime。
1 2 3 4 5 6 7 8 9 10 |
static NSString * WZX_Archiver_Name_Key =@"WZX_Archiver_Name_Key"; - (void)setWZX_Archiver_Name:(NSString *)WZX_Archiver_Name { //self設定一個key為WZX_Archiver_Name_Key的值等於WZX_Archiver_Name //函式最後一個值是屬性,這裡用copy objc_setAssociatedObject(self, &WZX_Archiver_Name_Key, WZX_Archiver_Name, OBJC_ASSOCIATION_COPY_NONATOMIC); } - (NSString *)WZX_Archiver_Name { //返回self以WZX_Archiver_Name_Key為key的值 return objc_getAssociatedObject(self, &WZX_Archiver_Name_Key); } |
這樣我們就成功給類別新增了一個成員變數。
然後我們在wzx_archiveToName:
方法時,self.WZX_Archiver_Name = name;
你就會發現還有一個問題,物件包含的子物件歸檔時也會走這個方法,這樣的話 self.WZX_Archiver_Name
會被更改,從而導致子物件歸檔的檔名不對。 所以我們必須要做判斷:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
- (BOOL)wzx_archiveToName:(NSString *)name { return [self wzx_archiveToName:name andIsSon:NO]; } - (BOOL)wzx_archiveToName:(NSString *)name andIsSon:(BOOL)isSon { if (isSon == NO) { //不是物件中的子物件 self.WZX_Archiver_Name = name; NSString * path = [[self class] getPath:name]; return [NSKeyedArchiver archiveRootObject:self toFile:path]; } else { return [NSKeyedArchiver archiveRootObject:self toFile:[[self class]getSonPath:name]]; } } |
子類就走wzx_archiveToName:path andIsSon:YES
方法。
這樣自動化歸檔部分就完成了。
自動化解檔
重寫initWithCoder方法
1 2 3 4 5 6 7 8 9 10 11 12 |
#pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-designated-initializers" - (instancetype)initWithCoder:(NSCoder *)aDecoder { NSArray * propertyArr = [self wzx_allProperty]; for (NSDictionary * propertyDic in propertyArr) { if ([self decodeWithType:propertyDic[@"type"] Name:propertyDic[@"name"] Coder:aDecoder]) { [self setValue:[self decodeWithType:propertyDic[@"type"] Name:propertyDic[@"name"] Coder:aDecoder] forKey:propertyDic[@"name"]]; } } return self; } #pragma clang diagnostic pop |
這裡先消除在類別中重寫init方法的警告. 然後因為NSObject是基類不能使用super,所以這裡沒用self = [super initWithCoder:aDecoder]
,但是我覺得是沒有影響的,NSObject類肯定不會使用XIB或者故事版,所以自然不會使用這個方法,那麼就不會有什麼影響。
解編碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
- (id)decodeWithType:(NSString *)type Name:(NSString *)name Coder:(NSCoder *)aDecoder { if ([self isObject:type]) { return [aDecoder decodeObjectOfClass:NSClassFromString(type) forKey:name]; } else if([type isEqualToString:@"int"]|| [type isEqualToString:@"short"]){ return @([aDecoder decodeIntegerForKey:name]); } else if([type isEqualToString:@"BOOL"]){ return @([aDecoder decodeBoolForKey:name]); } else if([type isEqualToString:@"float"]){ return @([aDecoder decodeFloatForKey:name]); } else if([type isEqualToString:@"double"]){ return @([aDecoder decodeDoubleForKey:name]); } else if([type isEqualToString:@"NSInteger"]|| [type isEqualToString:@"NSUInteger"]){ return @([aDecoder decodeIntegerForKey:name]); } if ([type hasPrefix:@"__Model__:"]) { // NSString * className = [type componentsSeparatedByString:@"__Model__:"][1]; // ; // NSString * docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; // NSString * path = [docPath stringByAppendingPathComponent:[NSString stringWithFormat:@"WZXArchiver/WZX_%@_%@_%@.archiver",NSStringFromClass(self.class),self.WZX_Archiver_Name,className]]; // [self setValue:[NSClassFromString(className) wzx_unArchiveToName:path isSon:YES]forKey:name]; } return nil; } - (BOOL)isObject:(NSString *)type { NSArray * objectTypeArr = @[@"NSString", @"NSMutableString", @"NSArray", @"NSMutableArray", @"NSDictionary", @"NSMutableDictionary", @"NSData", @"NSMutableData", @"NSSet", @"NSMutableSet"]; return [objectTypeArr containsObject:type]; } |
大致和編碼一致,根據型別分別編碼。 這裡有一點沒處理好:這裡我將對子物件的解檔註釋了,因為我在解檔的方法中對self. WZX_Archiver_Name
賦了值,而在解編碼的時候取回來的時候卻為nil。所以現在只能通過這個方法來解檔:
1 |
model.manModel = [ManModel wzx_unArchiveSonEntityToName:WZXArchiver_SonPath(@"Person", @"person1", @"ManModel", @"manModel")]; |
如果你有更好的處理方法歡迎評論!
現在自動化解檔就可以了。
使用效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
#import "WZXArchiver.h" PersonModel * model = [[PersonModel alloc]init]; //Object資料型別 model.str = @"str"; model.muStr = [NSMutableString stringWithString:@"muStr"]; model.dic = @{@"key":@"value"}; model.muDic = [NSMutableDictionary dictionaryWithDictionary:@{ @"key":@"muValue" }]; model.arr = @[@"arr1",@"arr2"]; model.muArr = [NSMutableArray arrayWithArray:@[@"muarr1",@"muarr2"]]; model.data = [model.str dataUsingEncoding:NSUTF8StringEncoding]; model.muData = [NSMutableData dataWithData:model.data]; model.set = [NSSet setWithObjects:@"1",@"2",@"3",nil]; model.muSet = [NSMutableSet setWithSet:model.set]; //基本資料型別 model.w_float = 1.1; model.w_doule = 2.2; model.w_cgfloat = 3.3; model.w_int = 4; model.w_integer = 5; model.w_uinteger = 6; model.w_bool = YES; //其他物件 ManModel * manModel = [[ManModel alloc]init]; manModel.name = @"wzx"; manModel.age = 23; model.manModel = manModel; //歸檔 [model wzx_archiveToName:@"person1"]; //解檔 PersonModel * model2 = [PersonModel wzx_unArchiveToName:@"person1"]; //若你物件中包含另一個物件 model2.manModel = [ManModel wzx_unArchiveSonEntityToName:WZXArchiver_SonPath(@"PersonModel", @"person1", @"ManModel", @"manModel")]; |
PS.
更多的細節可以看原始碼
原始碼地址 如果你喜歡這個專案,歡迎 star
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式