iOS 開發:『Runtime』詳解(四)獲取類詳細屬性、方法

行走少年郎發表於2019-08-01
  • 本文首發於我的個人部落格:『不羈閣』
  • 文章連結:傳送門
  • 本文更新時間:2019年07月31日20:29:30

本文用來介紹 iOS 開發中,如何通過『Runtime』獲取類詳細屬性、方法。通過本文,您將瞭解到:

  1. 獲取類詳細屬性、方法簡述
  2. 獲取類詳細屬性、方法(成員變數列表、屬性列表、方法列表、所遵循的協議列表)
  3. 應用場景 3.1 修改私有屬性 3.2 萬能控制器跳轉 3.3 實現字典轉模型 3.4 改進 iOS 歸檔和解檔

文中示例程式碼在: bujige / YSC-Class-DetailList-Demo


1. 獲取類詳細屬性、方法簡述

在蘋果官方為我們提供的類中,只能獲取一小部分公開的屬性和方法。有些我們恰好需要的屬性和方法,可能會被官方隱藏了起來,沒有直接提供給我們。

那應該如何才能獲取一個類中所有的變數和方法,用來查詢是否有對我們有用的變數和方法呢?

幸好 Runtime 中為我們提供了一系列 API 來獲取 Class (類)的 成員變數( Ivar )、屬性( Property )、方法( Method )、協議( Protocol ) 等。我們可以通過這些方法來遍歷一個類中的成員變數列表、屬性列表、方法列表、協議列表。從而查詢我們需要的變數和方法。

比如說遇到這樣一個需求:更改 UITextField 佔位文字的顏色和字號。實現程式碼參考 3.1 修改私有屬性 中的例子。

下面我們先來講解一下如何通過程式碼獲取類詳細屬性、方法。


2. 獲取類詳細屬性、方法

注意:標頭檔案中需引入 #import <objc/runtime.h>

2.1 獲取類的成員變數列表

// 列印成員變數列表
- (void)printIvarList {
    unsigned int count;
    
    Ivar *ivarList = class_copyIvarList([self class], &count);
    for (unsigned int i = 0; i < count; i++) {
        Ivar myIvar = ivarList[i];
        const char *ivarName = ivar_getName(myIvar);
        NSLog(@"ivar(%d) : %@", i, [NSString stringWithUTF8String:ivarName]);
    }
    
    free(ivarList);
}
複製程式碼

2.2 獲取類的屬性列表

// 列印屬性列表
- (void)printPropertyList {
    unsigned int count;
    
    objc_property_t *propertyList = class_copyPropertyList([self class], &count);
    for (unsigned int i = 0; i < count; i++) {
        const char *propertyName = property_getName(propertyList[i]);
        NSLog(@"propertyName(%d) : %@", i, [NSString stringWithUTF8String:propertyName]);
    }
    
    free(propertyList);
}
複製程式碼

2.3 獲取類的方法列表

// 列印方法列表
- (void)printMethodList {
    unsigned int count;
    
    Method *methodList = class_copyMethodList([self class], &count);
    for (unsigned int i = 0; i < count; i++) {
        Method method = methodList[i];
        NSLog(@"method(%d) : %@", i, NSStringFromSelector(method_getName(method)));
    }
    
    free(methodList);
}
複製程式碼

2.4 獲取類所遵循的協議列表

// 列印協議列表
- (void)printProtocolList {
    unsigned int count;
    
    __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
    for (unsigned int i = 0; i < count; i++) {
        Protocol *myProtocal = protocolList[i];
        const char *protocolName = protocol_getName(myProtocal);
        NSLog(@"protocol(%d) : %@", i, [NSString stringWithUTF8String:protocolName]);
    }
    
    free(protocolList);
}
複製程式碼

3. 應用場景

3.1 修改私有屬性

需求:更改 UITextField 佔位文字的顏色和字號

先來想想又幾種做法:

方法 1:通過 attributedPlaceholder 屬性修改

我們知道 UITextField 中有 placeholder 屬性和 attributedPlaceholder 屬性。通過 placeholder 屬性只能更改佔位文字,無法修改佔位文字的字型和顏色。而通過 attributedPlaceholder 屬性我們就可以修改 UITextField 佔位文字的顏色和字號了。

方法 2:重寫 UITextField 的 drawPlaceholderInRect: 方法修改

實現步驟:

  1. 自定義一個 XXTextField 繼承自 UITextField;
  2. 重寫自定義 XXTextField 的 drawPlaceholderInRect: 方法;
  3. 在 drawPlaceholderInRect 方法中設定 placeholder 的屬性。
- (void)drawPlaceholderInRect:(CGRect)rect {
    
    // 計算佔位文字的 Size
    NSDictionary *attributes = @{
                                 NSForegroundColorAttributeName : [UIColor lightGrayColor],
                                 NSFontAttributeName : [UIFont systemFontOfSize:15]
                                 };
    CGSize placeholderSize = [self.placeholder sizeWithAttributes:attributes];
    
    [self.placeholder drawInRect:CGRectMake(0, (rect.size.height - placeholderSize.height)/2, rect.size.width, rect.size.height) withAttributes: attributes];
}
複製程式碼

方法 3:利用 Runtime,找到並修改 UITextfield 的私有屬性

實現步驟:

  1. 通過獲取類的屬性列表和成員變數列表的方法列印 UITextfield 所有屬性和成員變數;
  2. 找到私有的成員變數 _placeholderLabel
  3. 利用 KVC 對 _placeholderLabel 進行修改。
// 列印 UITextfield 的所有屬性和成員變數
- (void)printUITextFieldList {
    unsigned int count;
    
    Ivar *ivarList = class_copyIvarList([UITextField class], &count);
    for (unsigned int i = 0; i < count; i++) {
        Ivar myIvar = ivarList[i];
        const char *ivarName = ivar_getName(myIvar);
        NSLog(@"ivar(%d) : %@", i, [NSString stringWithUTF8String:ivarName]);
    }
    
    free(ivarList);
    
    objc_property_t *propertyList = class_copyPropertyList([UITextField class], &count);
    for (unsigned int i = 0; i < count; i++) {
        const char *propertyName = property_getName(propertyList[i]);
        NSLog(@"propertyName(%d) : %@", i, [NSString stringWithUTF8String:propertyName]);
    }
    
    free(propertyList);
}

// 通過修改 UITextfield 的私有屬性更改佔位顏色和字型
- (void)createLoginTextField {
    UITextField *loginTextField = [[UITextField alloc] init];
    loginTextField.frame = CGRectMake(15,(self.view.bounds.size.height-52-50)/2, self.view.bounds.size.width-60-18,52);
    loginTextField.delegate = self;
    loginTextField.font = [UIFont systemFontOfSize:14];
    loginTextField.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
    loginTextField.textColor = [UIColor blackColor];
    
    loginTextField.placeholder = @"使用者名稱/郵箱";
    [loginTextField setValue:[UIFont systemFontOfSize:15] forKeyPath:@"_placeholderLabel.font"];
    [loginTextField setValue:[UIColor lightGrayColor]forKeyPath:@"_placeholderLabel.textColor"];
    
    [self.view addSubview:loginTextField];
}
複製程式碼

3.2 萬能控制器跳轉

需求:

  1. 某個頁面的不同 banner 圖,點選可以跳轉到不同頁面。
  2. 推送通知,點選跳轉到指定頁面。
  3. 二維碼掃描,根據不同內容,跳轉不同頁面。
  4. WebView 頁面,根據 URL 點選不同,跳轉不同的原生頁面。

先來思考一下幾種解決方法。

方法 1:在每個需要跳轉的地方寫一堆判斷語句以及跳轉語句。

方法 2:將判斷語句和跳轉語句抽取出來,寫到基類,或者對應的 Category 中。

方法 3:利用 Runtime,定製一個萬能跳轉控制器工具。

實現步驟:

  1. 事先和伺服器端商量好,定義跳轉不同控制器的規則,讓伺服器傳回對應規則的相關引數。 比如:跳轉到 A 控制器,需要伺服器傳回 A 控制器的類名,控制器 A 需要傳入的屬性引數(id、type 等等)。
  2. 根據伺服器傳回的類名,建立對應的控制器物件;
  3. 遍歷伺服器傳回的引數,利用 Runtime 遍歷控制器物件的屬性列表;
  4. 如果控制器物件存在該屬性,則利用 KVC 進行賦值;
  5. 進行跳轉。

首先,定義跳轉規則,如下所示。XXViewController 是將要跳轉的控制器類名。property 字典中儲存的是控制器所需的屬性引數。

// 定義的規則
NSDictionary *params = @{
                         @"class" : @"XXViewController",
                         @"property" : @{
                                 @"ID" : @"123",
                                 @"type" : @"XXViewController1"
                                 }
                         };
複製程式碼

然後,新增一個工具類 XXJumpControllerTool,新增跳轉相關的類方法。

/********************* XXJumpControllerTool.h 檔案 *********************/

#import <Foundation/Foundation.h>

@interface XXJumpControllerTool : NSObject

+ (void)pushViewControllerWithParams:(NSDictionary *)params;

@end


/********************* XXJumpControllerTool.m 檔案 *********************/

#import "XXJumpControllerTool.h"
#import <UIKit/UIKit.h>
#import <objc/runtime.h>

@implementation XXJumpControllerTool

+ (void)pushViewControllerWithParams:(NSDictionary *)params {
    // 取出控制器類名
    NSString *classNameStr = [NSString stringWithFormat:@"%@", params[@"class"]];
    const char *className = [classNameStr cStringUsingEncoding:NSASCIIStringEncoding];
    
    // 根據字串返回一個類
    Class newClass = objc_getClass(className);
    if (!newClass) {
        // 建立一個類
        Class superClass = [NSObject class];
        newClass = objc_allocateClassPair(superClass, className, 0);
        // 註冊你建立的這個類
        objc_registerClassPair(newClass);
    }

    // 建立物件(就是控制器物件)
    id instance = [[newClass alloc] init];
    
    NSDictionary *propertys = params[@"property"];
    [propertys enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
        // 檢測這個物件是否存在該屬性
        if ([XXJumpControllerTool checkIsExistPropertyWithInstance:instance verifyPropertyName:key]) {
            // 利用 KVC 對控制器物件的屬性賦值
            [instance setValue:obj forKey:key];
        }
    }];
    
    
    // 跳轉到對應的控制器
    [[XXJumpControllerTool topViewController].navigationController pushViewController:instance animated:YES];
}


// 檢測物件是否存在該屬性
+ (BOOL)checkIsExistPropertyWithInstance:(id)instance verifyPropertyName:(NSString *)verifyPropertyName {
    unsigned int count, i;
    
    // 獲取物件裡的屬性列表
    objc_property_t *properties = class_copyPropertyList([instance class], &count);
    
    for (i = 0; i < count; i++) {
        objc_property_t property =properties[i];
        //  屬性名轉成字串
        NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
        // 判斷該屬性是否存在
        if ([propertyName isEqualToString:verifyPropertyName]) {
            free(properties);
            return YES;
        }
    }
    free(properties);
    
    return NO;
}

// 獲取當前顯示在螢幕最頂層的 ViewController
+ (UIViewController *)topViewController {
    UIViewController *resultVC = [XXJumpControllerTool _topViewController:[[UIApplication sharedApplication].keyWindow rootViewController]];
    while (resultVC.presentedViewController) {
        resultVC = [XXJumpControllerTool _topViewController:resultVC.presentedViewController];
    }
    return resultVC;
}

+ (UIViewController *)_topViewController:(UIViewController *)vc {
    if ([vc isKindOfClass:[UINavigationController class]]) {
        return [XXJumpControllerTool _topViewController:[(UINavigationController *)vc topViewController]];
    } else if ([vc isKindOfClass:[UITabBarController class]]) {
        return [XXJumpControllerTool _topViewController:[(UITabBarController *)vc selectedViewController]];
    } else {
        return vc;
    }
    return nil;
}

@end
複製程式碼

測試程式碼:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 萬能跳轉控制器
    [self jumpController];
}
複製程式碼

3.3 實現字典轉模型

在日常開發中,將網路請求中獲取的 JSON 資料轉為資料模型,是我們開發中必不可少的操作。通常我們會選用諸如 YYModelJSONModel 或者 MJExtension 等第三方框架來實現這一過程。這些框架實現原理的核心就是 RuntimeKVC,以及 Getter / Setter

實現的大體思路如下:藉助 Runtime 可以動態獲取成員列表的特性,遍歷模型中所有屬性,然後以獲取到的屬性名為 key,在 JSON 字典中尋找對應的值 value;再使用 KVC 或直接呼叫 Getter / Setter 將每一個對應 value 賦值給模型,就完成了字典轉模型的目的。

需求:將伺服器返回的 JSON 字典轉為資料模型。

先準備一份待解析的 JSON 資料,內容如下:

{
    "id": "123412341234",
    "name": "行走少年郎",
    "age": "18",
    "weight": 120,
    "address": {
        "country": "中國",
        "province": "北京"
    },
    "courses": [
        {
            "name": "Chinese",
            "desc": "語文課"
        },
        {
            "name": "Math",
            "desc": "數學課"
        },
        {
            "name": "English",
            "desc": "英語課"
        }
    ]
}
複製程式碼

假設這就是伺服器返回的 JSON 資料,內容是一個學生的資訊。現在我們需要將該 JSON 字典轉為方便開發的資料模型。

從這份 JSON 中可以看出,字典中取值除了字串之外,還有陣列和字典。那麼在將字典轉換成資料模型的時候,就要考慮 模型巢狀模型模型巢狀模型陣列 的情況了。具體步驟如下:

3.3.1 建立模型

經過分析,我們總共需要三個模型: XXStudentModel、XXAdressModel、XXCourseModel。

/********************* XXStudentModel.h 檔案 *********************/
#import <Foundation/Foundation.h>
#import "NSObject+XXModel.h"
@class XXAdressModel, XXCourseModel;

@interface XXStudentModel : NSObject <XXModel>

/* 姓名 */
@property (nonatomic, copy) NSString *name;
/* 學生號 id */
@property (nonatomic, copy) NSString *uid;
/* 年齡 */
@property (nonatomic, assign) NSInteger age;
/* 體重 */
@property (nonatomic, assign) NSInteger weight;
/* 地址(巢狀模型) */
@property (nonatomic, strong) XXAdressModel *address;
/* 課程(巢狀模型陣列) */
@property (nonatomic, strong) NSArray *courses;

@end

/********************* XXStudentModel.m 檔案 *********************/
#import "XXStudentModel.h"
#import "XXCourseModel.h"

@implementation XXStudentModel

+ (NSDictionary *)modelContainerPropertyGenericClass {
    //需要特別處理的屬性
    return @{
             @"courses" : [XXCourseModel class],
             @"uid" : @"id"
             };
}

@end


/********************* XXAdressModel.h 檔案 *********************/
#import <Foundation/Foundation.h>

@interface XXAdressModel : NSObject

/* 國籍 */
@property (nonatomic, copy) NSString *country;
/* 省份 */
@property (nonatomic, copy) NSString *province;
/* 城市 */
@property (nonatomic, copy) NSString *city;

@end


/********************* XXAdressModel.m 檔案 *********************/
#import "XXAdressModel.h"

@implementation XXAdressModel

@end


/********************* XXCourseModel.h 檔案 *********************/
#import <Foundation/Foundation.h>

@interface XXCourseModel : NSObject

/* 課程名 */
@property (nonatomic, copy) NSString *name;
/* 課程介紹 */
@property (nonatomic, copy) NSString *desc;

@end

/********************* XXCourseModel.m 檔案 *********************/
#import "XXCourseModel.h"

@implementation XXCourseModel

@end
複製程式碼

3.3.2 在 NSObject 分類中實現字典轉模型

細心的你可能已經發現:上面的 XXStudentModel.h 檔案中匯入了 #import "NSObject+XXModel.h" 檔案,並且遵循了 <XXModel> 協議,並且在 XXStudentModel.m 檔案中實現了協議的 + (NSDictionary *)modelContainerPropertyGenericClass 方法。

NSObject+XXModel.hNSObject+XXModel.m 就是我們用來解決字典轉模型所建立的分類,協議中的 + (NSDictionary *)modelContainerPropertyGenericClass 方法用來告訴分類特殊欄位的處理規則,比如 id --> uid

/********************* NSObject+XXModel.h 檔案 *********************/
#import <Foundation/Foundation.h>

// XXModel 協議
@protocol XXModel <NSObject>

@optional
// 協議方法:返回一個字典,表明特殊欄位的處理規則
+ (nullable NSDictionary<NSString *, id> *)modelContainerPropertyGenericClass;

@end;

@interface NSObject (XXModel)

// 字典轉模型方法
+ (instancetype)xx_modelWithDictionary:(NSDictionary *)dictionary;

@end
複製程式碼
/********************* NSObject+XXModel.m 檔案 *********************/
#import "NSObject+XXModel.h"
#import <objc/runtime.h>

@implementation NSObject (XXModel)

+ (instancetype)xx_modelWithDictionary:(NSDictionary *)dictionary {
    
    // 建立當前模型物件
    id object = [[self alloc] init];
    
    unsigned int count;
    
    // 獲取當前物件的屬性列表
    objc_property_t *propertyList = class_copyPropertyList([self class], &count);
    
    // 遍歷 propertyList 中所有屬性,以其屬性名為 key,在字典中查詢 value
    for (unsigned int i = 0; i < count; i++) {
        // 獲取屬性
        objc_property_t property = propertyList[i];
        const char *propertyName = property_getName(property);
        
        NSString *propertyNameStr = [NSString stringWithUTF8String:propertyName];
        
        // 獲取 JSON 中屬性值 value
        id value = [dictionary objectForKey:propertyNameStr];
        
        // 獲取屬性所屬類名
        NSString *propertyType;
        unsigned int attrCount;
        objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount);
        for (unsigned int i = 0; i < attrCount; i++) {
            switch (attrs[i].name[0]) {
                case 'T': { // Type encoding
                    if (attrs[i].value) {
                        propertyType = [NSString stringWithUTF8String:attrs[i].value];
                        // 去除轉義字元:@\"NSString\" -> @"NSString"
                        propertyType = [propertyType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
                        // 去除 @ 符號
                        propertyType = [propertyType stringByReplacingOccurrencesOfString:@"@" withString:@""];
                        
                    }
                } break;
                default: break;
            }
        }
        
        // 對特殊屬性進行處理
        // 判斷當前類是否實現了協議方法,獲取協議方法中規定的特殊屬性的處理方式
        NSDictionary *perpertyTypeDic;
        if([self respondsToSelector:@selector(modelContainerPropertyGenericClass)]){
            perpertyTypeDic = [self performSelector:@selector(modelContainerPropertyGenericClass) withObject:nil];
        }
        
        // 處理:字典的 key 與模型屬性不匹配的問題,如 id -> uid
        id anotherName = perpertyTypeDic[propertyNameStr];
        if(anotherName && [anotherName isKindOfClass:[NSString class]]){
            value =  dictionary[anotherName];
        }

        // 處理:模型巢狀模型的情況
        if ([value isKindOfClass:[NSDictionary class]] && ![propertyType hasPrefix:@"NS"]) {
            Class modelClass = NSClassFromString(propertyType);
            if (modelClass != nil) {
                // 將被巢狀字典資料也轉化成Model
                value = [modelClass xx_modelWithDictionary:value];
            }
        }

        // 處理:模型巢狀模型陣列的情況
        // 判斷當前 value 是一個陣列,而且存在協議方法返回了 perpertyTypeDic
        if ([value isKindOfClass:[NSArray class]] && perpertyTypeDic) {
            Class itemModelClass = perpertyTypeDic[propertyNameStr];
            //封裝陣列:將每一個子資料轉化為 Model
            NSMutableArray *itemArray = @[].mutableCopy;
            for (NSDictionary *itemDic  in value) {
                id model = [itemModelClass xx_modelWithDictionary:itemDic];
                [itemArray addObject:model];
            }
            value = itemArray;
        }

        // 使用 KVC 方法將 value 更新到 object 中
        if (value != nil) {
            [object setValue:value forKey:propertyNameStr];
        }
        
    }
    free(propertyList);
    
    return object;
}

@end
複製程式碼

3.3.3 測試程式碼

- (void)parseJSON {
    
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"Student" ofType:@"json"];
    NSData *jsonData = [NSData dataWithContentsOfFile:filePath];

    // 讀取 JSON 資料
    NSDictionary *json = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
    NSLog(@"%@",json);

    // JSON 字典轉模型
    XXStudentModel *student = [XXStudentModel xx_modelWithDictionary:json];

    NSLog(@"student.uid = %@", student.uid);
    NSLog(@"student.name = %@", student.name);

    for (unsigned int i = 0; i < student.courses.count; i++) {
        XXCourseModel *courseModel = student.courses[i];
        NSLog(@"courseModel[%d].name = %@ .desc = %@", i, courseModel.name, courseModel.desc);
    }
}
複製程式碼

效果如下:

iOS 開發:『Runtime』詳解(四)獲取類詳細屬性、方法

當然,如若需要考慮快取機制、效能問題、物件型別檢查等,建議還是使用例如 YYModel 之類的知名第三方框架,或者自己造輪子。


3.4 改進 iOS 歸檔和解檔

『歸檔』是一種常用的輕量型檔案儲存方式,在專案中,如果需要將資料模型本地化儲存,一般就會用到歸檔和解檔。但是如果資料模型中有多個屬性的話,我們不得不對每個屬性進行處理,這個過程非常繁瑣。

這裡我們可以參考之前『字典轉模型』 的程式碼。通過 Runtime 獲取類的屬性列表,實現自動歸檔和解檔。歸檔操作和解檔操作主要會用到了兩個方法: encodeObject: forKey:decodeObjectForKey:

首先在 NSObject 的分類 NSObject+XXModel.hNSObject+XXModel.m 中新增以下程式碼:

// 解檔
- (instancetype)xx_modelInitWithCoder:(NSCoder *)aDecoder {
    if (!aDecoder) return self;
    if (!self) {
        return self;
    }
    
    unsigned int count;
    objc_property_t *propertyList = class_copyPropertyList([self class], &count);
    for (unsigned int i = 0; i < count; i++) {
        const char *propertyName = property_getName(propertyList[i]);
        NSString *name = [NSString stringWithUTF8String:propertyName];
        
        id value = [aDecoder decodeObjectForKey:name];
        [self setValue:value forKey:name];
    }
    free(propertyList);
    
    return self;
}

// 歸檔
- (void)xx_modelEncodeWithCoder:(NSCoder *)aCoder {
    if (!aCoder) return;
    if (!self) {
        return;
    }
    unsigned int count;
    objc_property_t *propertyList = class_copyPropertyList([self class], &count);
    for (unsigned int i = 0; i < count; i++) {
        const char *propertyName = property_getName(propertyList[i]);
        NSString *name = [NSString stringWithUTF8String:propertyName];
        
        id value = [self valueForKey:name];
        [aCoder encodeObject:value forKey:name];
    }
    free(propertyList);
}
複製程式碼

然後在需要實現歸檔解檔的模型中,新增 -initWithCoder:-encodeWithCoder: 方法。

#import "XXPerson.h"
#import "NSObject+XXModel.h"

@implementation XXPerson

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super init];
    if (self) {
        [self xx_modelInitWithCoder:aDecoder];
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [self xx_modelEncodeWithCoder:aCoder];
}

@end
複製程式碼

測試一下歸檔解檔程式碼:

XXPerson *person = [[XXPerson alloc] init];
person.uid = @"123412341234";
person.name = @"行走少年郎";
person.age = 18;
person.weight = 120;

// 歸檔
NSString *path = [NSString stringWithFormat:@"%@/person.plist", NSHomeDirectory()];
[NSKeyedArchiver archiveRootObject:person toFile:path];

// 解檔
XXPerson *personObject = [NSKeyedUnarchiver unarchiveObjectWithFile:path];

NSLog(@"personObject.uid = %@", personObject.uid);
NSLog(@"personObject.name = %@", personObject.name);
複製程式碼

當然,上邊程式碼只是演示一下 Runtime 對於歸檔和解檔的優化,真正用在開發中的邏輯遠比上邊的樣例要負責,具體也參考 YYModel 的實現。


參考資料


iOS 開發:『Runtime』詳解 系列文章:

尚未完成:

  • iOS 開發:『Runtime』詳解(五)Crash 防護系統
  • iOS 開發:『Runtime』詳解(六)Objective-C 2.0 結構解析
  • iOS 開發:『Runtime』詳解(七)KVO 底層實現

相關文章