runtime實現(xiàn)歸檔解檔代碼下載地址如下
可以直接拉入項目使用
為什么我們要學(xué)習(xí) objc/runtime
雖然 Object-C 是一門易上手的語言,但是對于深刻理解 OC 中的消息發(fā)送機制是很有必要的。我們需要理解怔毛,對于消息發(fā)送機制[target doMethodWith:var1]
, 是如何被 編譯為objec_msgSend(target, @selector(doMethodWith:), var1)
這種形式。理解 objec/runtime 運行時斥滤,可以加深我們對OC語言的理解闷祥,并且了解應(yīng)用程序是如何執(zhí)行的。
runtime 開源性
蘋果的 Object-C/runtime 為開源代碼拦耐,我們可以隨時從 蘋果開源網(wǎng)站 http://opensource.apple.com 獲取婶希。
歸檔榕暇、反歸檔
什么是歸檔? 反歸檔饲趋?
把 OC 中基本數(shù)據(jù)存儲到沙盒中拐揭,被稱為基本數(shù)據(jù)持久化。 基本數(shù)據(jù)奕塑,在 oc中有以下四種堂污,分別為 NSString 、 NSArray 龄砰、 NSDictionary 盟猖、 NSData。
字符串换棚、 數(shù)組式镐、 字典 和 數(shù)據(jù) 這四種類型,系統(tǒng)提供了 writeToFile: 直接寫入文件的方法固蚤, 因此可以直接存儲數(shù)據(jù)到沙盒中娘汞。
而對于 OC 中非基本數(shù)據(jù)類型,比如: UIImage 夕玩、 自定義類型Person 等你弦,系統(tǒng)并沒有提供 直接寫入文件的方法。 這就需要我們把 非基本數(shù)據(jù)類型 轉(zhuǎn)化為 NSData 燎孟,然后再存入到沙盒中禽作。
把 復(fù)雜類型 通過 編碼方式 轉(zhuǎn)化為 data 型數(shù)據(jù)的過程稱為,歸檔揩页。
把 已編碼的data 型數(shù)據(jù)旷偿, 通過解碼方式轉(zhuǎn)化為 原有復(fù)雜類型 的過程稱為,反歸檔。
歸檔的步驟
- 復(fù)雜類型 遵守 NSCoding 協(xié)議萍程, 實現(xiàn)協(xié)議里幢妄,編碼 和 解碼 方法。
歸檔 其實 是一個編碼過程尘喝,把復(fù)雜對象通過編碼轉(zhuǎn)化為data型數(shù)據(jù)磁浇。
反歸檔 其實是一個解碼過程斋陪, 把已歸檔的data型數(shù)據(jù)通過解碼重新生成 復(fù)雜對象朽褪。
因此, 復(fù)雜對象歸檔反歸檔的前提是遵守 NSCoding 協(xié)議无虚,并且實現(xiàn) 編碼缔赠,解碼方法。
比如: Person 類 ,遵守 NSCoding 協(xié)議
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
// 歸檔的前提:被歸檔的對象需要遵守編碼協(xié)議友题,并實現(xiàn)相應(yīng)方法
@interface Person : NSObject <NSCoding>
@property (nonatomic,assign) NSInteger age; // 年齡integer類型
@property (nonatomic,copy) NSString *name; // 姓名嗤堰,oc對象類型
@property (nonatomic,strong) UIImage *image; // 圖片,UIImage類型
@end
Person 類度宦,實現(xiàn) NSCoding 協(xié)議方法踢匣。
編碼方法,歸檔時觸發(fā)戈抄。
- (void)encodeWithCoder:(NSCoder *)aCoder
解碼方法离唬,反歸檔時觸發(fā)。
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
// 歸檔的過程是一個編碼的過程划鸽,對象內(nèi)部屬性需要進(jìn)行編碼
- (void)encodeWithCoder:(NSCoder *)aCoder
{
// 注意1:編碼時输莺,需要注意,不同類型的實例變量裸诽,使用不同的編碼方式
// person 中 age 為integer 類型嫂用, name 為 nsstring 類型,image 為 UIImage類型
// 注意2:編碼時丈冬,需要在編碼時加上標(biāo)記嘱函;編碼 data型數(shù)據(jù)時,不需要加標(biāo)記埂蕊。
[aCoder encodeInteger:_age forKey:@"age"]; // 類型:integer 標(biāo)記:age
[aCoder encodeObject:_name forKey:@"name"]; // 類型:nsstring 標(biāo)記:name
[aCoder encodeDataObject:UIImagePNGRepresentation(_image)]; // 類型:data 標(biāo)記:不需要
}
// 反歸檔往弓,是一個解碼過程。解碼過程粒梦,是一個重新創(chuàng)建對象的過程亮航,因此,解碼方法是一個初始化方法匀们。 init開頭
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
// 解碼缴淋,給內(nèi)部對象賦值, 選擇對應(yīng)類型的解碼方法 和 標(biāo)記解碼。
self.age = [aDecoder decodeIntegerForKey:@"age"];
self.name = [aDecoder decodeObjectForKey:@"name"];
self.image = [UIImage imageWithData:[aDecoder decodeDataObject]];
}
return self;
}
2. 歸檔
// 歸檔練習(xí)
- (void)archive{
Person *person1 = [[Person alloc]init];
person1.age = 16;
person1.name = @"吳書敏";
person1.image = [UIImage imageNamed:@"avatar.jpg"];
// 歸檔 , 歸檔是把 復(fù)雜對象轉(zhuǎn)化為一個mutableData 的過程重抖,轉(zhuǎn)化完畢后露氮,把mutaleData 寫入到文件
// 1. 創(chuàng)建可變數(shù)據(jù)
NSMutableData *data1 = [NSMutableData data];
// 2. 創(chuàng)建一個歸檔對象 NSKeyedArchiver
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data1];
// 3. 歸檔
[archiver encodeObject:person1 forKey:@"p1"];
// 4. 歸檔完成
[archiver finishEncoding];
// 5. 把歸檔完成的數(shù)據(jù)寫入到沙盒
NSString *dataPath = [NSHomeDirectory() stringByAppendingPathComponent:@"data1"];
NSLog(@"dataPath = %@",dataPath);
[data1 writeToFile:dataPath atomically:YES];
self.data = data1;
}
我們來歸納一下,如果要對一個復(fù)雜對象進(jìn)行歸檔钟沛。首先畔规,復(fù)雜對象要遵守 NSCoding 協(xié)議,并實現(xiàn)協(xié)議里的 編碼方法 和 解碼方法恨统。然后 歸檔叁扫。
我們來思考一個問題,如果一個類中有 100 個屬性畜埋,怎么實現(xiàn) 編碼 和 解碼方法莫绣? 如果讓每個屬性都 encode decode, 那么你至少要寫 200 行代碼悠鞍!
我們可以再思考一個問題对室,現(xiàn)在一個類中有 100 個屬性, 我現(xiàn)在的 應(yīng)用程序中咖祭,需要用到 10個類掩宜,這十個類都需要歸檔。如何實現(xiàn) 編碼 和 解碼方法么翰?
如果還讓每個類中每個屬性都 encode decode 牺汤,那么你至少要寫 10 * 100 = 1000 行代碼。
這顯然是不現(xiàn)實的硬鞍。
3.解檔
//解檔
- (void)Solution{
NSKeyedUnarchiver *unArchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:_data];
Person *p2 = [unArchiver decodeObjectForKey:@"p1"];
[unArchiver finishDecoding];
NSLog(@"%@", p2);
NSLog(@"%ld", p2.age);
NSLog(@"%@", p2.name);
NSLog(@"%@",p2.image);
UIImageView *imageView = [[UIImageView alloc] initWithFrame:(CGRectMake(100, 100, 200, 200))];
imageView.image = p2.image;
[self.view addSubview:imageView];
}
打印結(jié)果如下:
2016-12-26 02:17:22.055 NSCoding[4953:325316] <Person: 0x608000027b00>
2016-12-26 02:17:22.055 NSCoding[4953:325316] 16
2016-12-26 02:17:22.055 NSCoding[4953:325316] 吳書敏
2016-12-26 02:17:22.056 NSCoding[4953:325316] <UIImage: 0x608000282030>, {512, 384}
NSCoding
我們在 NSCoding 的協(xié)議方法里面做的操作如下:
編碼方法
- (void)encodeWithCoder:(NSCoder *)aCoder
里面做的操作,根據(jù)屬性類型慧瘤,對屬性進(jìn)行編碼。
加上編碼標(biāo)記,(秉著 見名知意的原則 標(biāo)記值與屬性名一樣)固该。
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
解碼方法里面做的操作,根據(jù)屬性類型,以及 編碼時加的標(biāo)記
锅减, 進(jìn)行解碼。
解碼完成后伐坏,給 屬性賦值(可以使用 kvc 賦值方法)怔匣。
因此如果我們:
獲取到每個屬性類型,就可以 根據(jù)類型對屬性進(jìn)行編碼桦沉。
獲取到每個屬性名每瞒, 就可以在編碼的時候添加 標(biāo)記。
有了標(biāo)記 和 屬性類型纯露,就可以正確的解碼剿骨。
objc/runtime 實現(xiàn)歸檔反歸檔
我們需要用到 動態(tài)運行時類庫中的以下幾種類型、函數(shù) 來獲取 屬性名埠褪,屬性類型浓利。
類型
**實例變量Ivar
**
表示類中的實例變量的一種類型挤庇。原類型為
struct objc_ivar *
///An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE;
char *ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
實例變量指針 Ivar *
表示 Ivar 類型指針,我們通常用來聲明 Ivar 類型數(shù)組贷掖。
比如:
用來存儲 類中所有實例變量的數(shù)組 varArray嫡秕。
Ivar *varArray = class_copyIvarList([self class], &count);
函數(shù)
獲取本類中所有實例變量函數(shù)class_copyIvarList
Ivar * class_copyIvarList(Class cls, unsigned int *outCount)
獲取一個數(shù)組Ivar *
, 該數(shù)組中含有本類中所有的實例變量苹威,但并不會獲取父類中聲明的實例變量昆咽。 當(dāng)不使用的時候 使用free()
釋放該數(shù)組。
獲取實例變量名ivar_getName
const char * ivar_getTypeEncoding( Ivar ivar)
返回一個 實例變量的名稱牙甫, 為 C 字符串掷酗。
獲取實例變量類型ivar_getTypeEncoding
const char * ivar_getTypeEncoding( Ivar ivar)
獲取實例變量的 編碼類型, 為 C 字符串腹暖。
下面我們按照上面歸檔反歸檔的分析出來的思路汇在,使用 runtime 中函數(shù)翰萨,寫一個通用的 NSObject 歸檔反歸檔分類脏答。
創(chuàng)建一個NSObject 的分類 NSObject+AGCoding
.h 文件
#import <Foundation/Foundation.h>
@interface NSObject (AGCoding) <NSCoding> // 遵守編碼協(xié)議
@end
.m 文件
#import "NSObject+AGCoding.h"
#import <objc/runtime.h>
#import <UIKit/UIKit.h>
/*
成員變量類型
獲取 實例變量的類型 方法
NSLog(@"%s", ivar_getTypeEncoding(array[i]));
*/
static NSString *intType = @"i"; // int
static NSString *integerType = @"q"; // long
static NSString *floatType = @"f"; // float
static NSString *doubleType = @"d"; // double
static NSString *boolType = @"B"; // bool
static NSString *imageType = @"UIImage"; // UIImage 類型
static NSString *stringType = @"NSString"; // NSString 類型
// 定義屬性字典,用來存儲 屬性名(key) 類型(value)
// 比如: age q
static NSMutableDictionary *proDic = nil;
@implementation NSObject (AGCoding)
// 歸檔是一個編碼的過程
- (void)encodeWithCoder:(NSCoder *)aCoder
{
// (1). 給字典分配空間
proDic = [NSMutableDictionary dictionary];
// (2). 獲取類中所有實例變量
unsigned int count; // 屬性個數(shù)
// 定義Ivar, copy
Ivar *varArray = class_copyIvarList([self class], &count);
// (3). for循環(huán)亩鬼,獲取屬性名稱 屬性類型
for (int i = 0; i < count; i++) {
Ivar var = varArray[i];
// 1. 獲取屬性名稱 : age name image
const char *cName = ivar_getName(var); // 屬性名c字符串
NSString *proName = [[NSString stringWithUTF8String:cName] substringFromIndex:1]; // OC字符串,并且去掉下劃線 _
// 2. 獲取屬性類型 : NSInteger NSString UIImage 等類型
const char *cType = ivar_getTypeEncoding(var); // 獲取變量類型殖告, c 字符串
// 3. kvc 取值, 用于 (5) 中判斷是否為 object 類型
id value = [self valueForKey:proName];
// 4. 把屬性類型 從 c 轉(zhuǎn)化為 oc 字符串
// c 字符串 轉(zhuǎn)化為 oc 字符串雳锋,會加上 @""
// 屬性類型
NSString *proType = [NSString stringWithUTF8String:cType]; // oc 字符串
// 5. 如果是OC類型數(shù)據(jù)黄绩,且不屬于 NSNumber類,把 @"" 去掉
if ([value isKindOfClass:[NSObject class]] && ![value isKindOfClass:[NSNumber class]]) {
// 截取前: @"@\"NSString\""
// 截取后: @\"NSString\"
NSUInteger length = proType.length;
proType = [proType substringWithRange:NSMakeRange(2, length - 3)];
}
// (4). proDic字典賦值 : 屬性名(key)_屬性類型(value)
if (![proName isEqualToString:@"proDic"]) {
[proDic setValue:proType forKey:proName];
}
// (5). 根據(jù)類型進(jìn)行編碼
if ([proType isEqualToString:integerType]) { // integer 類型
[aCoder encodeInteger:[value integerValue] forKey:proName];
} else if ([proType isEqualToString:imageType]) { // image 類型
[aCoder encodeDataObject:UIImagePNGRepresentation( value)];
} else if ([proType isEqualToString:stringType]) { // string 類型
[aCoder encodeObject:value forKey:proName];
}
// 若再有類型添加即可
} // for 循環(huán)玷过,獲取結(jié)束
// 釋放varArray
free(varArray);
}
// 反歸檔爽丹,是一個解碼的過程。
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [self init];
if (self) {
// 注意:解檔時重新給 proDic賦值
[self setProDic];
// 解碼辛蚊,給 屬性賦值
// 注意: 根據(jù)屬性類型解碼粤蝎,根據(jù)屬性名 kvc 賦值
// (1) 獲取 字典中的屬性名
for (NSString *proName in proDic) {
// (2) 獲取 屬性類型
NSString *proType = proDic[proName];
// (3) 解碼, kvc 賦值
if ([proType isEqualToString:integerType]) { // integer 類型
// 解碼
NSInteger number = [aDecoder decodeIntegerForKey:proName];
// 賦值
[self setValue:@(number) forKey:proName];
} else if ([proType isEqualToString:imageType]) { // image 類型
UIImage *image = [UIImage imageWithData:[aDecoder decodeDataObject]];
[self setValue:image forKey:proName];
} else if ([proType isEqualToString:stringType]) { // string 類型
NSString *string = [aDecoder decodeObjectForKey:proName];
[self setValue:string forKey:proName];
}
// 若再有類型添加即可
} // for 循環(huán)結(jié)束
}
return self;
}
// 解碼時重新給當(dāng)前類的proDic賦值
- (void)setProDic
{
// (1). 給字典分配空間
proDic = [NSMutableDictionary dictionary];
// (2). 獲取類中所有實例變量
unsigned int count; // 屬性個數(shù)
// 定義Ivar, copy
Ivar *varArray = class_copyIvarList([self class], &count);
// (3). for循環(huán)袋马,獲取屬性名稱 屬性類型
for (int i = 0; i < count; i++) {
Ivar var = varArray[i];
// 1. 獲取屬性名稱 : age name image
const char *cName = ivar_getName(var); // 屬性名c字符串
NSString *proName = [[NSString stringWithUTF8String:cName] substringFromIndex:1]; // OC字符串,并且去掉下劃線 _
// 2. 獲取屬性類型 : NSInteger NSString UIImage 等類型
const char *cType = ivar_getTypeEncoding(var); // 獲取變量類型初澎, c 字符串
// 3. 把屬性類型 從 c 轉(zhuǎn)化為 oc 字符串
// c 字符串 轉(zhuǎn)化為 oc 字符串,會加上 @""
// 屬性類型
NSString *proType = [NSString stringWithUTF8String:cType]; // oc 字符串
// 獲取 @ 的個數(shù)
NSArray *array = [proType componentsSeparatedByString:@"@"];
// 4. 如果屬性字符串中,多了 @"" 把 @"" 去掉
if (array.count >= 2) {
// 截取前: @"@\"NSString\""
// 截取后: @\"NSString\"
NSUInteger length = proType.length;
proType = [proType substringWithRange:NSMakeRange(2, length - 3)];
}
// (4). proDic字典賦值 : 屬性名(key)_屬性類型(value)
if (![proName isEqualToString:@"proDic"]) {
[proDic setValue:proType forKey:proName];
}
} // for 循環(huán)虑凛,獲取結(jié)束
// 釋放varArray
free(varArray);
}
@end