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

  • 本文首發於個人我的博客:『不羈閣』
  • 文章連接:傳送門
  • 本文更新時間:2019年07月31日20:29:30

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

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

文中示例代碼在: bujige / YSC-Class-DetailList-Demoios


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

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

那應該如何才能獲取一個類中全部的變量和方法,用來查找是否有對咱們有用的變量和方法呢?github

幸虧 Runtime 中爲咱們提供了一系列 API 來獲取 Class (類)的 成員變量( Ivar )、屬性( Property )、方法( Method )、協議( Protocol ) 等。咱們能夠經過這些方法來遍歷一個類中的成員變量列表、屬性列表、方法列表、協議列表。從而查找咱們須要的變量和方法。json

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

下面咱們先來說解一下如何經過代碼獲取類詳細屬性、方法。緩存


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

注意:頭文件中需引入 #import <objc/runtime.h>bash

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);
    }
}
複製代碼

效果以下:

固然,如若須要考慮緩存機制、性能問題、對象類型檢查等,建議仍是使用例如 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 底層實現
相關文章
相關標籤/搜索