近期學(xué)習(xí)IOS的runtime庫(kù)粉私,然后看到之前寫的NSCoding
協(xié)議有點(diǎn)復(fù)雜绷旗,如果屬性少還好,如果100多個(gè)屬性际乘,則會(huì)顯得麻煩坡倔。下面使用常規(guī)方式和使用Runtime
兩種方式進(jìn)行比較,然后總結(jié)一下中間遇到的坑脖含。
1.常規(guī)方法做歸檔與解檔
//自定義Person類繼承自NSObject
.h文件
@interface Person : NSObject<NSCoding>
@property(nonatomic,strong) NSString * name;//名字
@property(nonatomic,strong) NSString * gender;//性別
@property(nonatomic,strong) NSString * address;//地址
@property(nonatomic) NSUInteger age;//年齡
-(instancetype)initWithName:(NSString*)name gender:(NSString*)gender address:(NSString*)adderss age:(NSUInteger)age;
@end
.m文件
#import "Person.h"
@implementation Person
/*
使用常規(guī)進(jìn)行解檔與歸檔罪塔。
*/
-(void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:_name forKey:@"name"];
[aCoder encodeObject:_gender forKey:@"gender"];
[aCoder encodeObject:_address forKey:@"address"];
[aCoder encodeInteger:_age forKey:@"age"];
}
-(instancetype)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
_name = [aDecoder decodeObjectForKey:@"name"];
_gender = [aDecoder decodeObjectForKey:@"gender"];
_address = [aDecoder decodeObjectForKey:@"address"];
_age = [aDecoder decodeIntegerForKey:@"age"];
}
return self;
}
-(instancetype)initWithName:(NSString *)name gender:(NSString *)gender address:(NSString *)adderss age:(NSUInteger)age{
if (self = [super init]) {
_name = name;
_gender = gender;
_address = adderss;
_age = age;
}
return self;
}
-(NSString*)description{
return [NSString stringWithFormat:@"name:%@ gender:%@ age:%lu address:%@",self.name,self.gender,(unsigned long)self.age,self.address];
}
@end
上面定義了Person類,下面定義一個(gè)Teacher類繼承自Person類
//自定義Student繼承自Person類
.h文件
#import "Person.h"
@interface Teacher : Person
@property(nonatomic,strong) NSString * course;//課程
-(instancetype)initWithName:(NSString *)name gender:(NSString *)gender address:(NSString *)adderss age:(NSUInteger)age course:(NSString*)course;
@end
.m文件
#import "Teacher.h"
@implementation Teacher
-(instancetype)initWithCoder:(NSCoder *)aDecoder{
if (self = [super initWithCoder:aDecoder]) {
_course = [aDecoder decodeObjectForKey:@"course"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder{
[super encodeWithCoder:aCoder];
[aCoder encodeObject:_course forKey:@"course"];
}
-(instancetype)initWithName:(NSString *)name gender:(NSString *)gender address:(NSString *)adderss age:(NSUInteger)age course:(NSString *)course{
if (self = [super initWithName:name gender:gender address:adderss age:age]) {
_course = course;
}
return self;
}
-(NSString*)description{
NSString* str = [super description];
return [str stringByAppendingString:[NSString stringWithFormat:@" course:%@",self.course]];
}
@end
解檔與歸檔成對(duì)存在养葵,一定要都要實(shí)現(xiàn)征堪。當(dāng)一個(gè)類繼承自自定義的類,一定要調(diào)用父類的歸檔和解檔关拒,調(diào)用[super initWithCoder:aDeoder]
和[super encodeWithCoder:aCoder]
完成父類的歸檔與解檔佃蚜。
2.使用Runtime完成歸檔與解檔
只使用Runtime重構(gòu)了Person類庸娱,因?yàn)镾tudent類使用Runtime重構(gòu)沒有特別之處,與Person類相同谐算。
//.h文件熟尉,與上面定義相同
#import <Foundation/Foundation.h>
@interface Person : NSObject<NSCoding>
@property(nonatomic,strong) NSString * name;
@property(nonatomic,strong) NSString * gender;
@property(nonatomic,strong) NSString * address;
@property(nonatomic) NSUInteger age;
-(instancetype)initWithName:(NSString*)name gender:(NSString*)gender address:(NSString*)adderss age:(NSUInteger)age;
@end
.m文件
#import "Person.h"
#import <objc/runtime.h>
@implementation Person
/*
使用runtime進(jìn)行解檔與歸檔。
*/
-(void)encodeWithCoder:(NSCoder *)aCoder{
unsigned int count = 0;
Ivar *ivarLists = class_copyIvarList([Person class], &count);// 注意下面分析
for (int i = 0; i < count; i++) {
const char* name = ivar_getName(ivarLists[i]);
NSString* strName = [NSString stringWithUTF8String:name];
[aCoder encodeObject:[self valueForKey:strName] forKey:strName];
}
free(ivarLists); //一定不要忘了洲脂,自己釋放臣樱。
}
-(instancetype)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
unsigned int count = 0;
Ivar *ivarLists = class_copyIvarList([Person class], &count);
for (int i = 0; i < count; i++) {
const char* name = ivar_getName(ivarLists[i]);
NSString* strName = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
id value = [aDecoder decodeObjectForKey:strName];
[self setValue:value forKey:strName];
}
free(ivarLists);
}
return self;
}
-(instancetype)initWithName:(NSString *)name gender:(NSString *)gender address:(NSString *)adderss age:(NSUInteger)age{
if (self = [super init]) {
_name = name;
_gender = gender;
_address = adderss;
_age = age;
}
return self;
}
-(NSString*)description{
return [NSString stringWithFormat:@"name:%@ gender:%@ age:%lu address:%@",self.name,self.gender,(unsigned long)self.age,self.address];
}
@end
在實(shí)現(xiàn)上面注意點(diǎn)的時(shí)候,由于對(duì)Runtime系統(tǒng)中super
的理解不足腮考,導(dǎo)致開始寫的代碼時(shí)Ivar *ivarLists = class_copyIvarList([self class], &count);
雇毫,這樣在沒有子類繼承的情況下沒有錯(cuò)誤,完全可以使用踩蔚。但是如多有子類繼承這個(gè)類棚放,當(dāng)子類進(jìn)行解檔和歸檔的的時(shí)候就會(huì)出錯(cuò)。下面分析一下問(wèn)題:
1.解釋一些理論知識(shí)
(1)當(dāng)對(duì)一個(gè)類或者對(duì)象調(diào)用方法時(shí)候馅闽,在Runtime運(yùn)行時(shí)系統(tǒng)轉(zhuǎn)化成了發(fā)送消息飘蚯,編譯器在這時(shí)便根據(jù)情況在objc_msgSend()
,objc_msgSend_stret()
和objc_msgSendSuper()
,objc_msgSendSuper_stret()
四個(gè)函數(shù)選擇一種調(diào)用。如果消息是傳遞給父類福也,就調(diào)用帶super的方法局骤;如果消息返回?cái)?shù)據(jù)時(shí)數(shù)據(jù)結(jié)構(gòu)而不是簡(jiǎn)單值時(shí),調(diào)用名字帶stret的函數(shù)暴凑。
(2)隱藏的關(guān)鍵字self:當(dāng)objc_msgSend()
函數(shù)找到方法實(shí)現(xiàn)的時(shí)候峦甩,將消息的所有參數(shù)都傳遞給方法的實(shí)現(xiàn),同時(shí)還有兩個(gè)參數(shù)现喳,其中之一就是接受消息的對(duì)象self凯傲,其所指向的內(nèi)容是當(dāng)前對(duì)象的指針;另一個(gè)是方法選擇器_cmd,其所指向的內(nèi)容是當(dāng)前方法的SEL指針嗦篱。
(3)關(guān)鍵字super:super是super接受到消息時(shí)候冰单,編譯器創(chuàng)建的一個(gè)結(jié)構(gòu)體objc_super:struct objc_super{ id receiver, Class class}
,里面的receiver 其實(shí)就是self
的id指針,這個(gè)結(jié)構(gòu)體指定了消息應(yīng)該傳遞給指定的父類灸促。例如我們?nèi)绻{(diào)用[super class]
方法诫欠,獲取父類時(shí)候,編譯器實(shí)際是把self
的id指針和class
的SEL傳遞給了objc_msgSendSuper()
.相當(dāng)于self
調(diào)用父類的方法浴栽。這個(gè)時(shí)候[super class]
與[self class]
一樣荒叼。
總結(jié)一句就是使用super
的時(shí)候只是提示編譯器,我調(diào)用的是父類的方法吃度,真正的傳入的對(duì)象還是 self
甩挫。由于水平有限,很深入的分析達(dá)不到椿每,可能還有哪里說(shuō)的不對(duì)伊者,可以參考下面的講解英遭,我的有些內(nèi)容也是參考這里。http://www.reibang.com/p/1e06bfee99d0
(4)代碼Ivar *ivarLists = class_copyIvarList([Person class], &count);
生成實(shí)例變量的數(shù)組亦渗,不包含父類的挖诸。
2.分析原因
通過(guò)上面的基礎(chǔ)的知識(shí)的了解,應(yīng)該對(duì)知道大致的原因了法精。當(dāng)子類調(diào)用調(diào)用[super initWithCoder:aDeoder]
和[super encodeWithCoder:aCoder]
時(shí)候多律,相當(dāng)于使用self
對(duì)象調(diào)用父類的initWithCoder:
和encodeWithCoder:
方法。
這個(gè)時(shí)候如果使用Ivar *ivarLists = class_copyIvarList([self class], &count);
代碼搂蜓,self現(xiàn)在已經(jīng)是子類的對(duì)象的指針狼荞,現(xiàn)在[self class]
獲取的是子類Teacehr的類型,獲取的結(jié)果是Teacher類的實(shí)例變量列表帮碰,則父類的實(shí)例變量在子類沒有被歸檔與解檔相味,造成錯(cuò)誤,并不抱錯(cuò)殉挽,知識(shí)歸檔與解檔不完全丰涉,丟掉父類的內(nèi)容。
而下面的代碼一定要用self斯碌,因?yàn)槟闶菫樽宇怲eacher進(jìn)行歸檔和解檔的一死,需要傳遞Teacher對(duì)象的實(shí)例。
3.調(diào)用歸檔與解檔
#import "ViewController.h"
#import "Teacher.h"
#import "Person.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Teacher* teacher1 = [Teacher factoryWithName:@"liyang" gender:@"male" address:@"shandong" age:24 course:@"math"];
Teacher* teacher2 = [Teacher factoryWithName:@"li" gender:@"female" address:@"shanghai" age:23 course:@"english"];
NSMutableData* data = [NSMutableData data];
NSKeyedArchiver* archiver = [[NSKeyedArchiver alloc]initForWritingWithMutableData:data1];
[archiver encodeObject:teacher1 forKey:@"teacher"];
[archiver encodeObject:teacher2 forKey:@"teacher2"];
[archiver finishEncoding];
NSString* path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"teacher"];
[data writeToFile:path atomically:YES];
NSData* data2 = [NSData dataWithContentsOfFile:path];
NSKeyedUnarchiver* unarchiver = [[NSKeyedUnarchiver alloc]initForReadingWithData:data2];
Teacher* teacher3 = [unarchiver decodeObjectForKey:@"teacher"];
Teacher* teacher4 = [unarchiver decodeObjectForKey:@"teacher2"];
[unarchiver finishDecoding];
NSLog(@"\nteacher1:%@\nteacher3:%@\nteacher2:%@\nteacher4:%@\n%@",teacher1,teacher3,teacher2,teacher4,path);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
總的來(lái)說(shuō)使用Runtime庫(kù)來(lái)寫代碼傻唾,需要注意很多地方投慈,因?yàn)樘`活太強(qiáng)大,很難駕馭策吠。需要多學(xué)習(xí)里面的機(jī)制和理論才能好好使用逛裤。使用不當(dāng)就會(huì)掉入萬(wàn)劫不復(fù)之地。
使用NSObject類就能方便使用Runtime運(yùn)行時(shí)庫(kù)猴抹。
里面的內(nèi)容參考很多東西網(wǎng)上的知識(shí),自己歸納總結(jié)一下锁荔。
有錯(cuò)誤的地方往指正蟀给。感覺有用給個(gè)好評(píng)。
Runtime庫(kù)講解阳堕,請(qǐng)點(diǎn)擊下面鏈接:
http://www.reibang.com/p/1e06bfee99d0
http://www.reibang.com/p/25a319aee33d