objc/runtime之——歸檔反歸檔

以上原始手動解檔歸檔代碼下載地址如下

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ù)雜類型 的過程稱為,反歸檔。

歸檔的步驟

  1. 復(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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末碑宴,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子桑谍,更是在濱河造成了極大的恐慌延柠,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锣披,死亡現(xiàn)場離奇詭異贞间,居然都是意外死亡匕积,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進(jìn)店門榜跌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來闪唆,“玉大人,你說我怎么就攤上這事钓葫∏睦伲” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵础浮,是天一觀的道長帆调。 經(jīng)常有香客問我,道長豆同,這世上最難降的妖魔是什么番刊? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮影锈,結(jié)果婚禮上芹务,老公的妹妹穿的比我還像新娘。我一直安慰自己鸭廷,他們只是感情好枣抱,可當(dāng)我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著辆床,像睡著了一般佳晶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上讼载,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天轿秧,我揣著相機與錄音,去河邊找鬼咨堤。 笑死菇篡,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的吱型。 我是一名探鬼主播逸贾,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼津滞!你這毒婦竟也來了铝侵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤触徐,失蹤者是張志新(化名)和其女友劉穎咪鲜,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體撞鹉,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡疟丙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年颖侄,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片享郊。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡览祖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出炊琉,到底是詐尸還是另有隱情展蒂,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布苔咪,位于F島的核電站锰悼,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏团赏。R本人自食惡果不足惜箕般,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望舔清。 院中可真熱鬧丝里,春花似錦、人聲如沸鸠踪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽营密。三九已至,卻和暖如春目锭,著一層夾襖步出監(jiān)牢的瞬間评汰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工痢虹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留被去,地道東北人。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓奖唯,卻偏偏與公主長得像惨缆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子丰捷,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,055評論 2 355

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