IOS開發(fā)之NSCoding協(xié)議(使用runtime)

近期學(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.lxweimin.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.lxweimin.com/p/1e06bfee99d0
http://www.lxweimin.com/p/25a319aee33d

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,768評(píng)論 0 9
  • 對(duì)于從事 iOS 開發(fā)人員來(lái)說(shuō),所有的人都會(huì)答出【runtime 是運(yùn)行時(shí)】什么情況下用runtime?大部分人能...
    夢(mèng)夜繁星閱讀 3,732評(píng)論 7 64
  • 今天和朋友參加了EF的life clup ,主題就是啤酒,我酒量不行,酒精過(guò)敏體質(zhì),所以不是EF同學(xué)邀約我想我應(yīng)該...
    愛笑的胖妞閱讀 461評(píng)論 0 0
  • 常見的瀏覽器:1、IE瀏覽器2、Mozilla Firefox:擴(kuò)展插件功能強(qiáng)大3、Google Chrome:窗...
    不止風(fēng)雨閱讀 416評(píng)論 0 0
  • 分開大概有五個(gè)月了,還是會(huì)習(xí)慣想起你,吃飯的時(shí)候突然想起你不吃雞蛋,抽煙的時(shí)候記得你抽的牌子,洗頭的時(shí)候記得你用的...
    哦zyn親愛的閱讀 415評(píng)論 0 0