?楔子
Runtime是什么?見(jiàn)名知意署驻,其概念無(wú)非就是“因?yàn)?Objective-C 是一門(mén)動(dòng)態(tài)語(yǔ)言丹拯,所以它需要一個(gè)運(yùn)行時(shí)系統(tǒng)……這就是 Runtime 系統(tǒng)”云云赶盔。對(duì)博主這種菜鳥(niǎo)而言历恐,Runtime 在實(shí)際開(kāi)發(fā)中寸癌,其實(shí)就是一組C語(yǔ)言的函數(shù)。胡適說(shuō):“多研究些問(wèn)題弱贼,少談些主義”蒸苇,云山霧罩的概念聽(tīng)多了總是容易頭暈,接下來(lái)我們直接從代碼入手學(xué)習(xí) Runtime吮旅。
?
1溪烤、由objc_msgSend說(shuō)開(kāi)去:
Objective-C 中的方法調(diào)用,不是簡(jiǎn)單的方法調(diào)用庇勃,而是發(fā)送消息檬嘀,也就是說(shuō),其實(shí) [receiver message] 會(huì)被編譯器轉(zhuǎn)化為: objc_msgSend(receiver, selector)责嚷,何以證明鸳兽?新建一個(gè)類 MyClass,其.m文件如下:
#import "MyClass.h"
@implementation MyClass
-(instancetype)init{
if (self = [super init]) {
[self showUserName];
}
return self;
}
-(void)showUserName{
NSLog(@"Dave Ping");
}
使用 clang 重寫(xiě)命令:
$ clang -rewrite-objc MyClass.m
然后在同一目錄下會(huì)多出一個(gè) MyClass.cpp 文件罕拂,雙擊打開(kāi)揍异,可以看到 init 方法已經(jīng)被編譯器轉(zhuǎn)化為下面這樣:
static instancetype _I_MyClass_init(MyClass * self, SEL _cmd) {
if (self = ((MyClass *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("MyClass"))}, sel_registerName("init"))) {
((void (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("showUserName"));
}
return self;
}
我們要找的就是它:
((void (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("showUserName"))
objc_msgSend 函數(shù)被定義在 objc/message.h 目錄下,其函數(shù)原型是醬紫滴:
OBJC_EXPORT void objc_msgSend(void /* id self, SEL op, ... */ )
?該函數(shù)有兩個(gè)參數(shù)爆班,一個(gè) id 類型衷掷,一個(gè) SEL 類型。
2柿菩、SEL
SEL 被定義在 ?objc/objc.h 目錄下:
typedef struct objc_selector *SEL;
其實(shí)它就是個(gè)映射到方法的C字符串戚嗅,你可以用 Objective-C 編譯器命令 @selector() 或者 Runtime 系統(tǒng)的 sel_registerName 函數(shù)來(lái)獲得一個(gè) SEL 類型的方法選擇器。
3枢舶、id
與 SEL 一樣懦胞,id 也被定義在 ?objc/objc.h 目錄下:
typedef struct objc_object *id;
id 是一個(gè)結(jié)構(gòu)體指針類型,它可以指向 Objective-C 中的任何對(duì)象祟辟。objc_object 結(jié)構(gòu)體定義如下:
struct objc_object { Class isa OBJC_ISA_AVAILABILITY;};
我們通常所說(shuō)的對(duì)象医瘫,就長(zhǎng)這個(gè)樣子,這個(gè)結(jié)構(gòu)體只有一個(gè)成員變量? isa旧困,?對(duì)象可以通過(guò) ?isa 指針找到其所屬的類醇份。isa 是一個(gè) ?Class 類型的成員變量,那么 Class 又是什么呢吼具?
4僚纷、Class
Class 也是一個(gè)結(jié)構(gòu)體指針類型:
typedef struct objc_class *Class;
objc_class 結(jié)構(gòu)體是醬紫滴:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
我們通常說(shuō)的?類就長(zhǎng)這樣子:
·Class 也有一個(gè) isa 指針,指向其所屬的元類(meta).
·super_class:指向其超類.
·name:是類名.
·version:是類的版本信息.
·info:是類的詳情.
·instance_size:是該類的實(shí)例對(duì)象的大小.
·ivars:指向該類的成員變量列表.
·methodLists:指向該類的實(shí)例方法列表拗盒,它將方法選擇器和方法實(shí)現(xiàn)地址聯(lián)系起來(lái)怖竭。methodLists 是指向 ·objc_method_list 指針的指針,也就是說(shuō)可以動(dòng)態(tài)修改 *methodLists 的值來(lái)添加成員方法陡蝇,這也是 Category 實(shí)現(xiàn)的原理痊臭,同樣解釋了 Category 不能添加屬性的原因.
·cache:Runtime 系統(tǒng)會(huì)把被調(diào)用的方法存到 ?cache 中(理論上講一個(gè)方法如果被調(diào)用?哮肚,那么它有可能今后還會(huì)被調(diào)用),下次查找的時(shí)候效率更高.
·protocols:指向該類的協(xié)議列表.
說(shuō)到這里有點(diǎn)亂了广匙,我們來(lái)捋一下允趟,當(dāng)我們調(diào)用一個(gè)方法時(shí),其運(yùn)行過(guò)程大致如下:
首先鸦致,Runtime 系統(tǒng)會(huì)把方法調(diào)用轉(zhuǎn)化為消息發(fā)送潮剪,即 objc_msgSend,并且把方法的調(diào)用者分唾,和方法選擇器抗碰,當(dāng)做參數(shù)傳遞過(guò)去.
此時(shí),方法的調(diào)用者會(huì)通過(guò) isa 指針來(lái)找到其所屬的類绽乔,然后在 cache 或者 methodLists 中查找該方法弧蝇,找得到就跳到對(duì)應(yīng)的方法去執(zhí)行.
如果在類中沒(méi)有找到該方法,則通過(guò) super_class 往上一級(jí)超類查找(如果一直找到 NSObject 都沒(méi)有找到該方法的話迄汛,這種情況捍壤,我們放到后面消息轉(zhuǎn)發(fā)的時(shí)候再說(shuō)).
前面我們說(shuō) methodLists 指向該類的實(shí)例方法列表,實(shí)例方法即-方法鞍爱,那么類方法(+方法)存儲(chǔ)在哪兒呢鹃觉?類方法被存儲(chǔ)在元類中,Class 通過(guò) isa 指針即可找到其所屬的元類.
上圖實(shí)線是 super_class 指針睹逃,虛線是 isa 指針盗扇。根元類的超類是NSObject,而 isa 指向了自己沉填。NSObject 的超類為 nil疗隶,也就是它沒(méi)有超類。
5翼闹、使用objc_msgSend
前面我們使用 clang 重寫(xiě)命令斑鼻,看到 Runtime 是如何將方法調(diào)用轉(zhuǎn)化為消息發(fā)送的。我們也可以依樣畫(huà)葫蘆猎荠,來(lái)學(xué)習(xí)使用一下 objc_msgSend坚弱。新建一個(gè)類 TestClass,添加如下方法:
-(void)showAge{
NSLog(@"24");
}
-(void)showName:(NSString *)aName{
NSLog(@"name is %@",aName);
}
-(void)showSizeWithWidth:(float)aWidth andHeight:(float)aHeight{
NSLog(@"size is %.2f * %.2f",aWidth, aHeight);
}
-(float)getHeight{
return 187.5f;
}
-(NSString *)getInfo{
return @"Hi, my name is Dave Ping, I'm twenty-four years old in the year, I like apple, nice to meet you.";
}
我們可以像下面這樣关摇,使用 objc_msgSend 依次調(diào)用這些方法:
TestClass *objct = [[TestClass alloc] init];
((void (*) (id, SEL)) objc_msgSend) (objct, sel_registerName("showAge"));
((void (*) (id, SEL, NSString *)) objc_msgSend) (objct, sel_registerName("showName:"), @"Dave Ping");
((void (*) (id, SEL, float, float)) objc_msgSend) (objct, sel_registerName("showSizeWithWidth:andHeight:"), 110.5f, 200.0f);
float f = ((float (*) (id, SEL)) objc_msgSend_fpret) (objct, sel_registerName("getHeight"));
NSLog(@"height is %.2f",f);
NSString *info = ((NSString* (*) (id, SEL)) objc_msgSend) (objct, sel_registerName("getInfo"));
NSLog(@"%@",info);
也許你已經(jīng)注意到荒叶,objc_msgSend 在使用時(shí)都被強(qiáng)制轉(zhuǎn)換了一下,這是因?yàn)?objc_msgSend 這個(gè)函數(shù)至少要有兩個(gè)參數(shù)输虱,一個(gè)id消息接受者些楣,一個(gè)SEL消息名稱。后面三個(gè)點(diǎn)代表參數(shù),是變參愁茁。也就是說(shuō)方法攜帶的參數(shù)蚕钦,可以沒(méi)有,可以有多個(gè)埋市。如果我們把調(diào)用 showAge 方法改成這樣:
objc_msgSend(objct, sel_registerName("showAge"));
Xcode 就會(huì)報(bào)錯(cuò):
Too many arguments to function call, expected 0, have 2.
完整的 objc_msgSend 使用代碼在?這里冠桃。
6、objc_msgSendSuper
編譯器會(huì)根據(jù)情況在 objc_msgSend道宅,objc_msgSend_stret,objc_msgSendSuper胸蛛,objc_msgSendSuper_stret 或 objc_msgSend_fpret 五個(gè)方法中選擇一個(gè)來(lái)調(diào)用污茵。如果消息是傳遞給超類,那么會(huì)調(diào)用 objc_msgSendSuper 方法葬项,如果消息返回值是數(shù)據(jù)結(jié)構(gòu)泞当,就會(huì)調(diào)用 objc_msgSendSuper_stret 方法,如果返回值是浮點(diǎn)數(shù)民珍,則調(diào)用 objc_msgSend_fpret 方法襟士。
這里我們重點(diǎn)說(shuō)一下 objc_msgSendSuper,objc_msgSendSuper 函數(shù)原型如下:
OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
?當(dāng)我們調(diào)用 [super selector] 時(shí)嚷量,Runtime 會(huì)調(diào)用 objc_msgSendSuper 方法陋桂,objc_msgSendSuper 方法有兩個(gè)參數(shù),super 和 op蝶溶,Runtime 會(huì)把 selector 方法選擇器賦值給 op嗜历。而 super 是一個(gè) objc_super 結(jié)構(gòu)體指針,objc_super 結(jié)構(gòu)體定義如下:
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained Class class;
#else
__unsafe_unretained Class super_class;
#endif
/* super_class is the first class to search */
};
Runtime 會(huì)創(chuàng)建一個(gè) objc_spuer 結(jié)構(gòu)體變量抖所,將其地址作為參數(shù)(super)傳遞給 objc_msgSendSuper梨州,并且將 self 賦值給 receiver:super—>receiver=self.
舉個(gè)栗子,問(wèn)下面的代碼輸出什么:
@implementation Son : Father
- (id)init
{
self = [super init];
if (self)
{
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
答案是全部輸出 Son.
使用 clang 重寫(xiě)命令田轧,發(fā)現(xiàn)上述代碼被轉(zhuǎn)化為:
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_0, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_1, NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){ (id)self, (id)class_getSuperclass(objc_getClass("Son")) }, sel_registerName("class"))));
當(dāng)調(diào)用 [super class] 時(shí)暴匠,會(huì)轉(zhuǎn)換成 objc_msgSendSuper 函數(shù):
第一步先構(gòu)造 objc_super 結(jié)構(gòu)體,結(jié)構(gòu)體第一個(gè)成員就是 self傻粘。第二個(gè)成員是 (id)class_getSuperclass(objc_getClass(“Son”)).
第二步是去 Father 這個(gè)類里去找 - (Class)class每窖,沒(méi)有,然后去 NSObject 類去找抹腿,找到了岛请。最后內(nèi)部是使用 objc_msgSend(objc_super->receiver, @selector(class)) 去調(diào)用,此時(shí)已經(jīng)和 [self class] 調(diào)用相同了警绩,所以兩個(gè)輸出結(jié)果都是 Son崇败。
7、?對(duì)象關(guān)聯(lián)
對(duì)象關(guān)聯(lián)允許開(kāi)發(fā)者對(duì)已經(jīng)存在的類在 Category 中添加自定義的屬性:
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
·object 是源對(duì)象.
·value 是被關(guān)聯(lián)的對(duì)象.
·key 是關(guān)聯(lián)的鍵,objc_getAssociatedObject 方法通過(guò)不同的 key 即可取出對(duì)應(yīng)的被關(guān)聯(lián)對(duì)象.
·policy 是一個(gè)枚舉值后室,表示關(guān)聯(lián)對(duì)象的行為缩膝,從命名就能看出各個(gè)枚舉值的含義:
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
要取出被關(guān)聯(lián)的對(duì)象使用 objc_getAssociatedObject 方法即可,要?jiǎng)h除一個(gè)被關(guān)聯(lián)的對(duì)象岸霹,使用 objc_setAssociatedObject 方法將對(duì)應(yīng)的 key 設(shè)置成 nil 即可:
objc_setAssociatedObject(self, associatedKey, nil, OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_removeAssociatedObjects 方法將會(huì)移除源對(duì)象中所有的關(guān)聯(lián)對(duì)象.
舉個(gè)栗子疾层,假如我們要給 UIButton 添加一個(gè)監(jiān)聽(tīng)單擊事件的 block 屬性,新建 UIButton 的 Category贡避,其.m文件如下:
#import "UIButton+ClickBlock.h"
#import <objc/runtime.h>
static const void *associatedKey = "associatedKey";
@implementation UIButton (ClickBlock)
//Category中的屬性痛黎,只會(huì)生成setter和getter方法,不會(huì)生成成員變量
-(void)setClick:(clickBlock)click{
objc_setAssociatedObject(self, associatedKey, click, OBJC_ASSOCIATION_COPY_NONATOMIC);
[self removeTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
if (click) {
[self addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
}
}
-(clickBlock)click{
return objc_getAssociatedObject(self, associatedKey);
}
-(void)buttonClick{
if (self.click) {
self.click();
}
}
@end
然后在代碼中刮吧,就可以使用 UIButton 的屬性來(lái)監(jiān)聽(tīng)單擊事件了:
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = self.view.bounds;
[self.view addSubview:button];
button.click = ^{
NSLog(@"buttonClicked");
};
完整的對(duì)象關(guān)聯(lián)代碼點(diǎn)這里
8湖饱、?自動(dòng)歸檔
博主在學(xué)習(xí) Runtime 之前,歸檔的時(shí)候是醬紫寫(xiě)的:
- (void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeObject:self.ID forKey:@"ID"];
}
- (id)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
self.ID = [aDecoder decodeObjectForKey:@"ID"];
self.name = [aDecoder decodeObjectForKey:@"name"];
}
return self;
}
那么問(wèn)題來(lái)了杀捻,如果當(dāng)前 Model 有100個(gè)屬性的話井厌,就需要寫(xiě)100行這種代碼:
[aCoder encodeObject:self.name forKey:@"name"];
想想都頭疼,通過(guò) Runtime 我們就可以輕松解決這個(gè)問(wèn)題:
?1.使用 class_copyIvarList 方法獲取當(dāng)前 Model 的所有成員變量.
2.使用 ivar_getName 方法獲取成員變量的名稱.
3.通過(guò) KVC 來(lái)讀取 Model 的屬性值(encodeWithCoder:)致讥,以及給 Model 的屬性賦值(initWithCoder:).
舉個(gè)栗子仅仆,新建一個(gè) Model 類,其.m文件如下:
#import "TestModel.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation TestModel
- (void)encodeWithCoder:(NSCoder *)aCoder{
unsigned int outCount = 0;
Ivar *vars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar var = vars[i];
const char *name = ivar_getName(var);
NSString *key = [NSString stringWithUTF8String:name];
// 注意kvc的特性是垢袱,如果能找到key這個(gè)屬性的setter方法墓拜,則調(diào)用setter方法
// 如果找不到setter方法,則查找成員變量key或者成員變量_key惶桐,并且為其賦值
// 所以這里不需要再另外處理成員變量名稱的“_”前綴
id value = [self valueForKey:key];
[aCoder encodeObject:value forKey:key];
}
}
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
unsigned int outCount = 0;
Ivar *vars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar var = vars[i];
const char *name = ivar_getName(var);
NSString *key = [NSString stringWithUTF8String:name];
id value = [aDecoder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
}
return self;
}
@end
完整的自動(dòng)歸檔代碼在這里
9撮弧、字典與模型互轉(zhuǎn)
最開(kāi)始博主是這樣用字典給 Model 賦值的:
-(instancetype)initWithDictionary:(NSDictionary *)dict{
if (self = [super init]) {
self.age = dict[@"age"];
self.name = dict[@"name"];
}
return self;
}
可想而知,遇到的問(wèn)題跟歸檔時(shí)候一樣(后來(lái)使用MJExtension)姚糊,這里我們稍微來(lái)學(xué)習(xí)一下其中原理贿衍,字典轉(zhuǎn)模型的時(shí)候:
1.?根據(jù)字典的 key 生成 setter 方法.
2.使用 objc_msgSend 調(diào)用 setter 方法為 Model 的屬性賦值(或者 KVC).
模型轉(zhuǎn)字典的時(shí)候:
?1.調(diào)用 class_copyPropertyList 方法獲取當(dāng)前 Model 的所有屬性.
2.調(diào)用 property_getName 獲取屬性名稱.
3.根據(jù)屬性名稱生成 getter 方法.
4.使用 objc_msgSend 調(diào)用 getter 方法獲取屬性值(或者 KVC).
代碼如下:
#import "NSObject+KeyValues.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation NSObject (KeyValues)
//字典轉(zhuǎn)模型
+(id)objectWithKeyValues:(NSDictionary *)aDictionary{
id objc = [[self alloc] init];
for (NSString *key in aDictionary.allKeys) {
id value = aDictionary[key];
/*判斷當(dāng)前屬性是不是Model*/
objc_property_t property = class_getProperty(self, key.UTF8String);
unsigned int outCount = 0;
objc_property_attribute_t *attributeList = property_copyAttributeList(property, &outCount);
objc_property_attribute_t attribute = attributeList[0];
NSString *typeString = [NSString stringWithUTF8String:attribute.value];
if ([typeString isEqualToString:@"@\"TestModel\""]) {
value = [self objectWithKeyValues:value];
}
/**********************/
//生成setter方法,并用objc_msgSend調(diào)用
NSString *methodName = [NSString stringWithFormat:@"set%@%@:",[key substringToIndex:1].uppercaseString,[key substringFromIndex:1]];
SEL setter = sel_registerName(methodName.UTF8String);
if ([objc respondsToSelector:setter]) {
((void (*) (id,SEL,id)) objc_msgSend) (objc,setter,value);
}
}
return objc;
}
//模型轉(zhuǎn)字典
-(NSDictionary *)keyValuesWithObject{
unsigned int outCount = 0;
objc_property_t *propertyList = class_copyPropertyList([self class], &outCount);
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
for (int i = 0; i < outCount; i ++) {
objc_property_t property = propertyList[i];
//生成getter方法救恨,并用objc_msgSend調(diào)用
const char *propertyName = property_getName(property);
SEL getter = sel_registerName(propertyName);
if ([self respondsToSelector:getter]) {
id value = ((id (*) (id,SEL)) objc_msgSend) (self,getter);
/*判斷當(dāng)前屬性是不是Model*/
if ([value isKindOfClass:[self class]] && value) {
value = [value keyValuesWithObject];
}
/**********************/
if (value) {
NSString *key = [NSString stringWithUTF8String:propertyName];
[dict setObject:value forKey:key];
}
}
}
return dict;
}
@end
完整代碼在這里
10贸辈、?動(dòng)態(tài)方法解析
前面我們留下了一點(diǎn)東西沒(méi)說(shuō),那就是如果某個(gè)對(duì)象調(diào)用了不存在的方法時(shí)會(huì)怎么樣肠槽,一般情況下程序會(huì)crash擎淤,錯(cuò)誤信息類似下面這樣:
unrecognized selector sent to instance 0x7fd0a141afd0
但是在程序crash之前,Runtime 會(huì)給我們動(dòng)態(tài)方法解析的機(jī)會(huì)秸仙,消息發(fā)送的步驟大致如下:
1.檢測(cè)這個(gè) selector 是不是要忽略的嘴拢。比如 Mac OS X 開(kāi)發(fā),有了垃圾回收就不理會(huì) retain寂纪,release 這些函數(shù)了.
2.檢測(cè)這個(gè) target 是不是 nil 對(duì)象席吴。ObjC 的特性是允許對(duì)一個(gè) nil 對(duì)象執(zhí)行任何一個(gè)方法不會(huì) Crash赌结,因?yàn)闀?huì)被忽略掉.
3.如果上面兩個(gè)都過(guò)了,那就開(kāi)始查找這個(gè)類的 IMP孝冒,先從 cache 里面找柬姚,完了找得到就跳到對(duì)應(yīng)的函數(shù)去執(zhí)行.
如果 cache 找不到就找一下方法分發(fā)表.
4.如果分發(fā)表找不到就到超類的分發(fā)表去找,一直找庄涡,直到找到NSObject類為止.
如果還找不到就要開(kāi)始進(jìn)入消息轉(zhuǎn)發(fā)了量承,消息轉(zhuǎn)發(fā)的大致過(guò)程如圖:1.進(jìn)入 resolveInstanceMethod: 方法,指定是否動(dòng)態(tài)添加方法穴店。若返回NO撕捍,則進(jìn)入下一步,若返回YES迹鹅,則通過(guò) class_addMethod 函數(shù)動(dòng)態(tài)地添加方法卦洽,消息得到處理,此流程完畢.
2.resolveInstanceMethod: 方法返回 NO 時(shí)斜棚,就會(huì)進(jìn)入 forwardingTargetForSelector: 方法,這是 Runtime 給我們的第二次機(jī)會(huì)该窗,用于指定哪個(gè)對(duì)象響應(yīng)這個(gè) selector弟蚀。返回nil,進(jìn)入下一步酗失,返回某個(gè)對(duì)象义钉,則會(huì)調(diào)用該對(duì)象的方法.
3.若 forwardingTargetForSelector: 返回的是nil,則我們首先要通過(guò) methodSignatureForSelector: 來(lái)指定方法簽名规肴,返回nil捶闸,表示不處理,若返回方法簽名拖刃,則會(huì)進(jìn)入下一步.
4當(dāng)?shù)?methodSignatureForSelector: 方法返回方法簽名后删壮,就會(huì)調(diào)用 forwardInvocation: 方法,我們可以通過(guò) anInvocation 對(duì)象做很多處理兑牡,比如修改實(shí)現(xiàn)方法央碟,修改響應(yīng)對(duì)象等.
如果到最后,消息還是沒(méi)有得到響應(yīng)均函,程序就會(huì)crash亿虽,詳細(xì)代碼在這里。