RunTime的瞭解與使用

runtime這個詞對於iOS程序猿童鞋來講,都是一個「耳熟能詳」的名詞,由於runtime就像面試中的「詛咒」同樣,每當遇到相關面試題,都是幾家歡喜幾家愁。那麼runtime究竟是什麼呢? runtime是 OC底層的一套C語言的API,編譯器最終都會將OC代碼轉化爲運行時代碼。不過蘋果已經將 ObjC runtime 代碼開源了,咱們能夠下面的網址瀏覽源代碼:html

opensource.apple.com/source/objc…面試

那麼咱們就先經過一些經典的面試題來了解一些runtime這個「詛咒」的威力:數組

一、下面代碼輸出結果是什麼?安全

@implementation Son : Father
- (id)init {
    self = [super init];
    if (self) {
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@", NSStringFromClass([super class]));
    }
    return self;
}
@end
複製代碼

答案:(1) Son / Son 由於super爲編譯器標示符,向super發送的消息被編譯成objc_msgSendSuper,但仍以self做爲receiverbash

二、下面代碼輸出結果是什麼?網絡

BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
複製代碼

答案:(2) YES / NO / NO / NO <NSObject>協議有一套類方法的隱藏實現,因此編譯運行正常;因爲NSObject meta class的父類爲NSObject class,因此只有第一句爲YES (3) 下面的代碼會?Compile Error / Runtime Crash / NSLog…?app

@interface NSObject (Sark)
+ (void)foo;
@end
@implementation NSObject (Sark)
- (void)foo {
    NSLog(@"IMP: -[NSObject (Sark) foo]");
}
@end
// 測試代碼
[NSObject foo];
[[NSObject new] foo];
複製代碼

答案(3) 編譯運行正常,兩行代碼都執行-foo。 [NSObject foo]方法查找路線爲 NSObject meta class –super-> NSObject class,和第二題知識點很類似。 (4) 下面的代碼會?Compile Error / Runtime Crash / NSLog…?框架

@interface Sark : NSObject
@property (nonatomic, copy) NSString *name;
@end

@implementation Sark
- (void)speak {
    NSLog(@"my name's %@", self.name);
}
@end

@implementation ThirdViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    id cls = [Sark class];
    void *obj = &cls;
    [(__bridge id)obj speak];
}
@end
複製代碼

效果.png

答案 編譯運行正常,輸出ThirdViewController中的self對象。 編譯運行正常,調用了-speak方法,因爲 id cls = [Sark class]; void *obj = &cls; obj已經知足了構成一個objc對象的所有要求(首地址指向ClassObject),因此可以正常走消息機制; 因爲這我的造的對象在棧上,而取self.name的操做本質上是self指針在內存向高位地址偏移(32位下一個指針是4字節),按viewDidLoad執行時各個變量入棧順序從高到底爲(self, _cmd, self.class, self, obj)(前兩個是方法隱含入參,隨後兩個爲super調用的兩個壓棧參數),遂棧低地址的obj+4取到了self。函數

看到上面的幾道面試題和答案,有些童鞋可能仍是一頭霧水,那下面就一點點的捋一捋runtime的功能:工具

一:RunTime中的一些名詞概念

objc_msgSend函數定義以下: id objc_msgSend(id self, SEL op, ...)

(1)什麼是 SEL?

打開objc.h文件,看下SEL的定義以下: typedef struct objc_selector *SEL SEL是一個指向objc_selector結構體的指針。而objc_selector的定義並無在runtime.h中給出定義。咱們能夠嘗試運行以下代碼:

SEL sel = @selector(foo);
NSLog(@"%s", (char *)sel);
NSLog(@"%p", sel);
const char *selName = [@"foo" UTF8String];
SEL sel2 = sel_registerName(selName);
NSLog(@"%s", (char *)sel2);
NSLog(@"%p", sel2);
複製代碼

輸出以下:

2014-11-06 13:46:08.058 Test[15053:1132268] foo
2014-11-06 13:46:08.058 Test[15053:1132268] 0x7fff8fde5114
2014-11-06 13:46:08.058 Test[15053:1132268] foo
2014-11-06 13:46:08.058 Test[15053:1132268] 0x7fff8fde5114
複製代碼

Objective-C在編譯時,會根據方法的名字生成一個用來區分這個方法的惟一的一個ID。只要方法名稱相同,那麼它們的ID就是相同的。 因此兩個類之間,無論它們是父類與子類的關係,仍是之間沒有這種關係,只要方法名相同,那麼它的SEL就是同樣的。每個方法都對應着一個SEL。編譯器會根據每一個方法的方法名爲那個方法生成惟一的SEL。這些SEL組成了一個Set集合,當咱們在這個集合中查找某個方法時,只須要去找這個方法對應的SEL便可。而SEL本質是一個字符串,因此直接比較它們的地址便可。 固然,不一樣的類能夠擁有相同的selector。不一樣類的實例對象執行相同的selector時,會在各自的方法列表中去根據selector去尋找本身對應的IMP

(2)那麼什麼是IMP呢?繼續看定義:

typedef id (*IMP)(id, SEL, ...); IMP本質就是一個函數指針,這個被指向的函數包含一個接收消息的對象id,調用方法的SEL,以及一些方法參數,並返回一個id。所以咱們能夠經過SEL得到它所對應的IMP,在取得了函數指針以後,也就意味着咱們取得了須要執行方法的代碼入口,這樣咱們就能夠像普通的C語言函數調用同樣使用這個函數指針。

(3)那麼什麼是Ivar呢?

ivar 在objc中被定義爲: typedef struct objc_ivar *Ivar; 它是一個指向objc_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
} OBJC2_UNAVAILABLE;
複製代碼
(4)@Property

類中的Property屬性被編譯器轉換成了Ivar,而且自動添加了咱們熟悉的Set和Get方法。

(5)isa

isa 是一個 objc_class 類型的指針,內存佈局以一個 objc_class 指針爲開始的全部東東均可以當作一個 object 來對待! 這就是說 objc_class 或者說類其實也能夠當作一個 objc_object 對象來對待!這裏要區分清楚兩個名詞:類對象(class object)實例對象(instance object)。ObjC還對類對象實例對象中的 isa 所指向的類結構做了不一樣的命名: 類對象中的 isa 指向類結構被稱做 metaclass(元類),metaclass 存儲類的static類成員變量與static類成員方法(+開頭的方法); 實例對象中的 isa 指向類結構稱做 class(普通的),class 結構存儲類的普通成員變量與普通成員方法(-開頭的方法)。

(6)super_class:

一看就明白,指向該類的父類唄!若是該類已是最頂層的根類(如 NSObject 或 NSProxy),那麼 super_class 就爲 NULL。 好,先中斷一下其餘類結構成員的介紹,讓咱們釐清一下在繼承層次中,子類,父類,根類(這些都是普通 class)以及其對應的 metaclass 的 isa 與 super_class 之間關係:

` 規則一:類的實例對象的 isa 指向該類;該類的 isa 指向該類的 metaclass;

規則二:類的 super_class 指向其父類,若是該類爲根類則值爲 NULL;

規則三:metaclass 的 isa 指向根 metaclass,若是該 metaclass 是根 metaclass 則指向自身;

規則四:metaclass 的 super_class 指向父 metaclass,若是該 metaclass 是根 metaclass 則指向該 metaclass 對應的類; `

(7)那麼 class 與 metaclass 的區別

class 是 instance object 的類類型。當咱們向實例對象發送消息(實例方法)時,咱們在該實例對象的 class 結構的 methodlists 中去查找響應的函數,若是沒找到匹配的響應函數則在該 class 的父類中的 methodlists 去查找(查找鏈爲上圖的中間那一排)。以下面的代碼中,向str 實例對象發送 lowercaseString 消息,會在 NSString 類結構的 methodlists 中去查找 lowercaseString 的響應函數。

NSString * str;
[str lowercaseString];
複製代碼

metaclass 是 class object 的類類型。當咱們向類對象發送消息(類方法)時,咱們在該類對象的 metaclass 結構的 methodlists 中去查找響應的函數,若是沒有找到匹配的響應函數則在該 metaclass 的父類中的 methodlists 去查找。以下面的代碼中,向 NSString 類對象發送 stringWithString 消息,會在 NSString 的 metaclass 類結構的 methodlists 中去查找 stringWithString 的響應函數。 [NSString stringWithString:@"str"];

二:Category添加屬性

category在咱們實際開發過程當中,是一個很是實用的得力助手,由於咱們能夠在不改變原有類的狀況下進行方法拓展,可是有些時候,咱們也須要爲這些category分類添加一些屬性供咱們使用,問題就在這裏,咱們都知道:category分類沒法直接添加屬性,可是咱們開發中又有這樣的需求,這就尷尬啦~~~ 首先咱們先看一下,爲何在category中沒法直接添加屬性呢,category是表示一個指向分類的結構體的指針,其定義以下:

struct objc_category {
    char *category_name  OBJC2_UNAVAILABLE;// 分類名
    char *class_name OBJC2_UNAVAILABLE;// 分類所屬的類名
    struct objc_method_list *instance_methods OBJC2_UNAVAILABLE;// 實例方法列表
    struct objc_method_list *class_methods OBJC2_UNAVAILABLE;// 類方法列表
    struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;// 分類所實現的協議列表
}
複製代碼

在這個結構體主要包含了分類定義的實例方法與類方法,其中

instance_methods列表是objc_class中方法列表的一個子集,

class_methods列表是元類方法列表的一個子集。

可發現,類別中沒有ivar(ivar表明類中實例變量的類型)成員變量指針,也就意味着:類別中不可以添加實例變量和屬性,struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;// 該類的成員變量鏈表 簡單的理解就是:在category中,系統沒法自動爲@property的屬性生成 set、get方法。 【有些人可能就有疑惑了:爲何官方API和一些第三方的框架工具都有爲category添加屬性的現象呢?好比:NSIndexPath (UITableView)、MJRefresh等等】因此這是咱們就須要使用到runtime爲category關聯一些屬性對象。

/** 
 *經過鍵值對關聯對象
 * 
 * @param object  關聯的對象源【一般爲 self】
 * @param key 惟一鍵,經過這個鍵進行取值【const void *是用來起到聲明做用】
 * @param value 值;「 key 」所對應的值
 * @param policy 內存管理策略,枚舉:objc_AssociationPolicy
 * 
 * @see objc_setAssociatedObject
 * @see objc_removeAssociatedObjects
 */
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);

/** 
 * 經過 key 獲取關聯值.
 * 
 * @param object 關聯的對象源【一般爲 self】,在設置關聯時所指定的與哪一個對象關聯的那個對象
 * @param key 惟一鍵,在設置關聯時所指定的鍵
 * 
 * @return 返回惟一鍵對應的 value 值
 * 
 * @see objc_setAssociatedObject
 */
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);

/** 
 * 取消屬性關聯對象
 * 
 * @param object 要取消關聯屬性的對象
 * 
 * @see objc_setAssociatedObject
 * @see objc_getAssociatedObject
 */
OBJC_EXPORT void objc_removeAssociatedObjects(id object)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
複製代碼

其中的關聯策略也是系統提供的一個枚舉:

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**表示弱引用關聯,一般是基本數據類型 */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**表示強引用關聯對象,是線程安全的; 如同:(nonatomic,strong) */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**表示關聯對象copy,是線程安全的; 如同:(nonatomic,copy) */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**表示強引用關聯對象,不是線程安全的;  如同:(atomic,strong)*/
    OBJC_ASSOCIATION_COPY = 01403          /**表示關聯對象copy,不是線程安全的; 如同:(nonatomic,copy)  */
};
複製代碼

舉個小栗子 ~

.h 中聲明
///經過runtime關聯數組屬性
@property (nonatomic,strong)NSMutableArray * array;

.m 中實現set、get 方法
/// 設置屬性關聯
-(void)setArray:(NSMutableArray *)array{
    objc_setAssociatedObject(self, @selector(array),
                             array, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSMutableArray *)array{
    return objc_getAssociatedObject(self, @selector(array));
}
複製代碼

設置調用關聯屬性.png
set方法被調用.png
get方法被調用.png
當運行程序就能夠發現:經過上面的runtime能夠完美的爲category關聯咱們須要的屬性。 ###三:方法交換 說到方法交換,這個也是在實際開發中常常用到的一個技能,好比:對象默認調用的系統方法API,可是咱們又須要對這個方法的調用及實現進行處理的狀況下,就可使用到 runtime的方法交換 method_exchangeImplementations進行實現這個需求。首先來看一些 runtime.h中提供的幾個方法:

/** 
 * 返回一個指定類對象實現的實例方法。
 * 
 * @param cls 指定的類
 * @param name 須要檢索的方法
 * 
 */
OBJC_EXPORT Method class_getInstanceMethod(Class cls, SEL name)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0);

/** 
 * 返回一個指定類對象實現的 類方法。
 * 
 * @param cls 指定的類
 * @param name 須要檢索的方法
 * 
 */
OBJC_EXPORT Method class_getClassMethod(Class cls, SEL name)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0);
/** 
 * 交換兩個方法的實現.
 * 
 * @param m1 方法與第二個方法交換。
 * @param m2 方法與第一個方法交換。
 * 
 */
OBJC_EXPORT void method_exchangeImplementations(Method m1, Method m2) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
複製代碼

舉一個小栗子:

類方法交換.png
對象方法交換.png
從代碼運行的效果能夠看出:方法交換以後,再調用原來的 eat 方法,在實現過程當中,就會默認進入到交換的 goToSchool 方法內。這就是關於 runtime方法交換的一個簡單示例,在實際開發中能夠根據本身的需求進行實際操做啦! 當類被引用進項目的時候就會執行load函數(在main函數開始執行以前),與這個類是否被用到無關,每一個類的load函數只會自動調用一次.也就是load函數是系統自動加載的,load方法會在runtime加載類、分類時調用。 ###四:動態類型判斷 即運行時再決定對象的類型。這類動態特性在平常應用中很是常見,簡單說就是id類型。id類型即通用的對象類,任何對象均可以被id指針所指,而在實際使用中,每每使用introspection來肯定該對象的實際所屬類: - (BOOL)isMemberOfClass:(Class)aClass是 NSObject 的方法,用以肯定某個 NSObject 對象是不是某個類的成員。 - (BOOL)isKindOfClass:(Class)aClass是用以肯定某個對象是否是某個類或其子類的成員;例如:

id obj = someInstance;
if ([obj isKindOfClass:someClass])
{
    someClass *classSpecifiedInstance = (someClass *)obj;
    // Do Something to classSpecifiedInstance which now is an instance of someClass
    //...
}
複製代碼

五:動態綁定方法

首先先看一下系統提供的動態綁定相關的幾個方法:

/**
 當這個類被調用了一個沒有實現的方法時,會調用到這裏
 @param sel 未實現的類方法名
 */
+ (BOOL)resolveClassMethod:(SEL)sel  OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
/**
 當這個類被調用了一個沒有實現的對象
 @param sel 未實現的對象方法名
 */
+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
/**
     動態添加
     class_addMethod([self class], @selector(resolveThisMethodDynamically), (IMP) myMethodIMP, "v@:");
     
     一、Class cls:類的類型
     二、name:方法標記 sel
     三、imp:方法的實現,是一個 函數指針
     四、type:返回值類型
     */
OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp,  const char *types)   OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

/**
上述添加方法中的 IMP imp 所對應的函數格式以下:
*/
void myMethodIMP(id self, SEL _cmd)
{
    // implementation ....
}
複製代碼

動態綁定所作的,便是在實例所屬類肯定後,將某些屬性和相應的方法綁定到實例上。這裏所指的屬性和方法固然包括了原來沒有在類中實現的,而是在運行時才須要的新加入的實現。在Cocoa層,咱們通常向一個NSObject對象發送-respondsToSelector:或者-instancesRespondToSelector:等來肯定對象是否能夠對某個SEL作出響應,而在OC消息轉發機制被觸發以前,對應的類的+resolveClassMethod:和+resolveInstanceMethod:將會被調用,在此時有機會動態地向類或者實例添加新的方法,也即類的實現是能夠動態綁定的。舉個小栗子:

動態添加方法.png
從運行結果上能夠看出:當對象調用一個該類沒有的方法時,系統會自動調用 resolveInstanceMethod :方法,而後咱們可使用 class_addMethod方法進行動態綁定,從而達到預期效果。 其中須要注意的是: performSelector: withObject: 方法,由於調用這個方法時,系統在編譯時是不會進行校驗方法是否存在,只有在運行時纔會進行查詢方法。而直接調用方法時,在編譯過程當中就會進行校驗方法是否存在。 ###六:獲取對象屬性 最典型的用法就是一個對象在 歸檔 encodeWithCoder解檔initWithCoder:方法中須要該對象全部的屬性進行 encodeObject:decodeObjectForKey:,經過runtime咱們聲明中不管寫多少個屬性,都不須要再修改實現中的代碼了。

得到某個類的全部成員變量(outCount 會返回成員變量的總數)
參數:
/**
一、哪一個類
二、放一個接收值的地址,用來存放屬性的個數
三、返回值:存放全部獲取到的屬性,經過下面兩個方法能夠調出名字和類型
*/
Ivar *class_copyIvarList(Class cls , unsigned int *outCount)
//得到成員變量的名字
const char *ivar_getName(Ivar v)
//得到成員變量的類型(除了基本數據類型)
const char *ivar_getTypeEndcoding(Ivar v)
複製代碼

舉個小栗子:

// C語言內  但凡看到 copy creat new 須要釋放
// ARC
// 告訴系統歸檔哪些東西
- (void)encodeWithCoder:(NSCoder *)coder
{
    unsigned int count = 0;
    Ivar * ivars = class_copyIvarList([Person class], &count);
    for (int i = 0; i < count; i ++) {
        //取出對應的成員Ivar
        Ivar ivar = ivars[i];
        const char * name = ivar_getName(ivar);
        const char *type = ivar_getTypeEncoding(ivar);
        NSLog(@"成員變量名:%s 成員變量類型:%s",name,type);
        
        NSString * key = [NSString stringWithUTF8String:name];
        //歸檔
        [coder encodeObject:[self valueForKey:key] forKey:key];
    }
    free(ivars);
}
//解檔
- (instancetype)initWithCoder:(NSCoder *)coder
{
    if (self =[super init]) {
        unsigned int count = 0;
        Ivar * ivars = class_copyIvarList([Person class], &count);
        for (int i = 0; i < count; i ++) {
            //取出對應的成員Ivar
            Ivar ivar = ivars[i];
            const char * name = ivar_getName(ivar);
            NSString * key = [NSString stringWithUTF8String:name];
            //解檔
            id value = [coder decodeObjectForKey:key];
            //設置到屬性身上
            [self setValue:value forKey:key];
        }
        free(ivars);
        
    }
    return self;
}
複製代碼

這就是使用runtime實現的歸檔解檔方法。 ###七:字典轉模型 字典轉模型這個在實際開發中也是最經常使用的,由於當咱們網絡請求到數據後,咱們就須要將數據轉化爲對應的對象模型。不過如今有比較成熟的字典轉模型框架,好比:YYModelMJExtension等,那咱們如何利用runtime實現本身的字典轉模型框架呢?舉個小栗子:

NSObject+Runtime_Model.h 代碼邏輯:

@interface NSObject (Runtime_Model)
/** 字典轉模型
 使用該方法進行字典轉模型時,若是使用了設置屬性名字與返回的 key 不一樣時,
 須要實現 - (void)setValue:(id)value forUndefinedKey:(NSString *)key 方法,進行轉化
 */
+ (instancetype)objectWithDict:(NSDictionary *)dict;
@end
複製代碼
NSObject+Runtime_Model.m 代碼邏輯:

#import "NSObject+Runtime_Model.h"
#import <objc/runtime.h>

@implementation NSObject (Runtime_Model)
// 字典轉模型
+ (instancetype)objectWithDict:(NSDictionary *)dict{
    // 建立對應模型對象
    id objc = [[self alloc] init];
    
    // 判斷字典中的 key 是否爲成員變量,以便爲成員變量進行替換賦值
    for (NSString * key in dict.allKeys) {
        
        id value = dict[key];
        
        /*判斷當前屬性是否是Model*/
        objc_property_t property = class_getProperty([self class], key.UTF8String);
        unsigned int outCount = 0;
        objc_property_attribute_t * attributeList = property_copyAttributeList(property, &outCount);
        
        if (!attributeList) {// 屬於模型的屬性
            
            if ([objc respondsToSelector:@selector(setValue:forUndefinedKey:)]) {
                [objc setValue:value forUndefinedKey:key];
            }
        }
    }
    unsigned int count = 0;
    
    // 1.獲取成員屬性數組
    Ivar *ivarList = class_copyIvarList(self, &count);
    
    // 2.遍歷全部的成員屬性名,一個一個去字典中取出對應的value給模型屬性賦值
    for (int i = 0; i < count; i++) {
        
        // 2.1 獲取成員屬性
        Ivar ivar = ivarList[i];
        
        // 2.2 獲取成員屬性名 C -> OC 字符串
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        
        // 2.3 _成員屬性名 => 字典key
        NSString *key = [ivarName substringFromIndex:1];
        
        // 2.4 去字典中取出對應value給模型屬性賦值
        id value = dict[key];
        
        // 屬性對應的類名
        const char *type = ivar_getTypeEncoding(ivar);
        
        // 獲取成員屬性類型
        NSString *ivarType = [NSString stringWithUTF8String:type];
        
        // 二級轉換,字典中還有字典,也須要把對應字典轉換成模型
        //
        // 判斷下value,是否是字典
        if ([value isKindOfClass:[NSDictionary class]] && ![ivarType containsString:@"NS"]) { //  是字典對象,而且屬性名對應類型是自定義類型
            // user User
            
            // 處理類型字符串 @\"User\" -> User ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""]; ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
            // 自定義對象,而且值是字典
            // value:user字典 -> User模型
            // 獲取模型(user)類對象
            Class modalClass = NSClassFromString(ivarType);
            
            // 字典轉模型
            if (modalClass) {
                // 字典轉模型 user
                value = [modalClass objectWithDict:value];
            }
            
            // 字典,user
            //            NSLog(@"%@",key);
        }
        
        // 三級轉換:NSArray中也是字典,把數組中的字典轉換成模型.
        // 判斷值是不是數組
        if ([value isKindOfClass:[NSArray class]]) {
            
            // 轉換成id類型,就能調用任何對象的方法
            id idSelf = self;
            
            ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
            ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
            
            // 生成模型
            Class classModel = NSClassFromString(ivarType);
            NSMutableArray *arrM = [NSMutableArray array];
            // 遍歷字典數組,生成模型數組
            for (NSDictionary *dict in value) {
                // 字典轉模型
                id model =  [classModel objectWithDict:dict];
                [arrM addObject:model];
            }
            // 把模型數組賦值給value
            value = arrM;
            
        }
        // 2.5 KVC字典轉模型
        if (value) {
            [objc setValue:value forKey:key];
        }
    }
    // 返回對象
    return objc;
    
}
@end
複製代碼

固然這也只是RunTime的一部分功能,但倒是在實際開發中常常用到的知識點。因此若是想要繼續拓展RunTime技能深度的話,能夠翻看蘋果開源的RunTime源碼http://opensource.apple.com/source/objc4/objc4-493.9/runtime/

參考 :

devclub.cc/article?art… www.cnblogs.com/gaoxiaoniu/…

相關文章
相關標籤/搜索