靜態(tài)綁定:編譯的時(shí)候就已經(jīng)確定了調(diào)用的方法。函數(shù)地址其實(shí)是硬編碼在指令中。
動(dòng)態(tài)綁定:運(yùn)行期才確定要調(diào)用的方法。
1.消息調(diào)用
調(diào)用過(guò)程過(guò)程

(1)發(fā)送sendMessage:params消息給object對(duì)象;
(2)OC消息通過(guò)編譯器轉(zhuǎn)換成了C語(yǔ)言函數(shù);
消息傳遞機(jī)制中的核心函數(shù),叫做objc_msgSend,其“原型”如下:
void object_msgSend(id self, SEL cmd, ...)
OC中給對(duì)象發(fā)消息可以這樣來(lái)寫(xiě):
id returnValue = [someObject messageName: parameter];
someObject是接收者; messageName是選擇子(方法名),選擇子+參數(shù)合起來(lái)稱(chēng)為消息
編譯器會(huì)將其轉(zhuǎn)化為C語(yǔ)言的如下函數(shù):
id returnValue = objc_msgSend(someObject, @selector(message:), params);
(3)在接收者someObject所屬的類(lèi)中,查詢(xún)選擇子selector(message:);
(4)如果沒(méi)有找到,就去super class中查找;
(5)如果最終還是沒(méi)有,就啟動(dòng)消息轉(zhuǎn)發(fā)機(jī)制,將消息轉(zhuǎn)發(fā)給別的對(duì)象。
(6)objc_msgSend會(huì)將匹配結(jié)果緩存在“快速映射表中”(fast map),每個(gè)類(lèi)都有一個(gè)表,方便再次查找時(shí)快速查找。
2.消息轉(zhuǎn)發(fā)機(jī)制
原理
(1)如果類(lèi)想了解某條消息,它需要實(shí)現(xiàn)相對(duì)應(yīng)的方法才行。
(2)由于編譯期間不會(huì)進(jìn)行動(dòng)態(tài)綁定方法,所以如果沒(méi)有實(shí)現(xiàn)對(duì)應(yīng)方法的話,是不會(huì)抱錯(cuò)的,因?yàn)榭梢赃\(yùn)行期間添加方法。
(3)如果運(yùn)行期間也沒(méi)有添加相應(yīng)的方法的話,會(huì)啟動(dòng)“消息轉(zhuǎn)發(fā)”直接,來(lái)讓其他對(duì)象處理這條消息。
(4)但是如果其他對(duì)象也不能處理的話,程序就會(huì)抱錯(cuò):
unrecognized selector sent to instance XXX
因?yàn)橄⒌慕邮照撸╥nstance)并沒(méi)有實(shí)現(xiàn)對(duì)應(yīng)的方法,不能理解這條消息(selector)。
消息轉(zhuǎn)發(fā)過(guò)程(分為兩個(gè)階段)

第一階段:動(dòng)態(tài)方法解析
(1)對(duì)象收到無(wú)法解析的message后,首先調(diào)用所屬類(lèi)的下列方法:
+ (BOOL)resolveInstanceMethod:(SEL)sel
+ (BOOL)resolveClassMethod:(SEL)sel
該方法會(huì)決定,是否新增一個(gè)實(shí)例方法來(lái)處理sel這個(gè)選擇子;
前提是:該方法的視線代碼已經(jīng)寫(xiě)好了,只要在運(yùn)行時(shí)動(dòng)態(tài)插入到類(lèi)里面即可。
(2)備援接收者
調(diào)用如下方法
- (id)forwardingTargetForSelector:(SEL)aSelector
若能當(dāng)前接收者能找到備援對(duì)象,則將其返回;若找不到,就返回nil。
一個(gè)對(duì)象內(nèi)部,可能有其他對(duì)象(實(shí)例變量),可以通過(guò)上面的方法,將內(nèi)部能處理aSelector的對(duì)象返回,這樣就好想該對(duì)象親自處理了aSelector一樣。
我們無(wú)法操作經(jīng)由這一步所轉(zhuǎn)發(fā)的消息,如果想要在發(fā)送給備援接收者之前先修改消息內(nèi)容,那就得通過(guò)完整的消息轉(zhuǎn)發(fā)機(jī)制來(lái)做了
第二階段:完整的消息轉(zhuǎn)發(fā)機(jī)制
(1)創(chuàng)建 NSInvocation對(duì)象:該對(duì)象封裝了selector、target、pramaters
(2)調(diào)用方法:
- (void)forwardInvocation:(NSInvocation *)anInvocation
該方法多用于改變消息內(nèi)容,比如追加pramaters,改變selector.
(3)如果本類(lèi)不能處理,則應(yīng)調(diào)用super class中的同名方法,直至NSObject
(4)如果調(diào)用了NSObject類(lèi)的方法,就會(huì)繼續(xù)調(diào)用
doesNotRecognizeSelector:
然后拋出異常,表明selector最終未能得到處理
(5)步驟越往后,處理消息的代價(jià)就越大。盡量在第一步就處理完,然后將該方法緩存起來(lái),以便之后快速處理。
下面是一個(gè)代碼示例
比如我們想給UIButton添加屬性,比如name或order,用來(lái)描述button。
(1)首先定義一個(gè)類(lèi)繼承自UIButton
@interface XZButton : UIButton
@property (nonatomic ,strong) NSString *name;
@property (nonatomic ,strong) NSNumber *order;
@end
(2)在XZButton.m文件中導(dǎo)入下面的頭文件,這樣才能使用running time的函數(shù)。
#import <objc/runtime.h>
(3)注意使用 @dynamic,告訴編譯器不用自動(dòng)生成存取方法。
@dynamic name,order;
(4)聲明一個(gè)NSMutableDictionary屬性,用來(lái)存儲(chǔ)要添加的屬性,這里要注意了,既然是使用字典存儲(chǔ),那么如果你想添加NSInteger屬性的話是會(huì)抱錯(cuò)的,因?yàn)樽值洳辉试S添加整型做為value(?)
@property (nonatomic ,strong) NSMutableDictionary *backingStore;
(5)init 方法中初始化字典
- (id)init{
if (self = [super init]) {
_backingStore = [NSMutableDictionary dictionary];
}
return self;
}
(6)注意這步是重點(diǎn),調(diào)用了resolveInstanceMethod方法,也就是上面說(shuō)的,詢(xún)問(wèn)receiver能否動(dòng)態(tài)添加對(duì)應(yīng)的方法來(lái)處理selector。
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSString *selectorString = NSStringFromSelector(sel);
if ([selectorString hasPrefix:@"set"]) {
class_addMethod(self,sel,(IMP)xZAutoDictionarySetter,"v@:@");
}else{
class_addMethod(self, sel, (IMP)xZAutoDictionaryGetter, "@ @:");
}
return YES;
}
(7)上面通過(guò)sel來(lái)判斷存取方法,然后在下面實(shí)現(xiàn)對(duì)應(yīng)的存取的方法
//set
void xZAutoDictionarySetter(id self,SEL _cmd,id value){
XZButton *typeSelf = (XZButton *)self;
NSMutableDictionary *backingStore = typeSelf.backingStore;
NSString *selectorString = NSStringFromSelector(_cmd);
NSMutableString *key = [selectorString mutableCopy];
//remove :
[key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)];
//remove set
[key deleteCharactersInRange:NSMakeRange(0, 3)];
//首字母小寫(xiě)
NSString *lowercaseFirstChar = [[key substringToIndex:1] lowercaseString];
[key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];
if (value) {
[backingStore setValue:value forKey:key];
}else{
[backingStore removeObjectForKey:key];
}
}
//get
id xZAutoDictionaryGetter(id self, SEL _cmd){
XZButton *typeSelf = (XZButton *)self;
NSMutableDictionary *backingStore = typeSelf.backingStore;
NSString *key = NSStringFromSelector(_cmd);
return [backingStore valueForKey:key];
}
(8)注意,這里添加的get和set方法名不能重復(fù),也就是不同的類(lèi)中不能使用相同的方法名。否則會(huì)報(bào)錯(cuò)( duplicate symbol)。這里我也沒(méi)太搞懂,不知道是不是因?yàn)槭荂函數(shù),所以不能重名,一般的OC方法,在不同的類(lèi)中是可以重名的,有明白的高人還請(qǐng)講解一下
(9)使用
XZButton *button = [[XZButton alloc] init];
button.name = @"xuz";
button.order = [NSNumber numberWithInteger:14];
NSLog(@"button's name is %@ order is %ld",button.name,(long)[button.order integerValue]);
3.消息處理
(1)方法調(diào)配:處理selector的方法可以在運(yùn)行期改變。既不需要源碼,也不用通過(guò)子類(lèi)繼承來(lái)復(fù)寫(xiě)方法改變這個(gè)類(lèi)本身的功能。并且新功能對(duì)本類(lèi)的所有實(shí)例生效,而不是僅限于復(fù)寫(xiě)了相關(guān)方法的那些子類(lèi)實(shí)例。
(2)類(lèi)的方法列表會(huì)把selector映射到相關(guān)的實(shí)現(xiàn)方法之上,可以通過(guò)這張表來(lái)找到應(yīng)該調(diào)用的方法。這些方法均以函數(shù)指針的形式來(lái)表示,這種指針叫做IMP,原型如下:
id (*IMP) (id,SEL,....)
(3)映射表如下
(4)OC運(yùn)行期間可以向表中添加新的selector(并實(shí)現(xiàn)對(duì)應(yīng)的方法);也可以更改selector映射的IMP指針指向的方法;還可以交換兩個(gè)selector映射的指針。
(5)舉例子,如何交換兩個(gè)方法的實(shí)現(xiàn),這里創(chuàng)建了Nsstring的分類(lèi)ExchangeString,并實(shí)現(xiàn)了一個(gè)方法,用來(lái)和lowercaseString交換實(shí)現(xiàn)的方法
#import "NSString+ExchangeString.h"
#import <objc/runtime.h>
@implementation NSString (ExchangeString)
- (NSString *)eoc_myLowercaseString{
//獲取selector對(duì)應(yīng)的IMP, 注意這里是class_getInstanceMethod(實(shí)例方法),我一開(kāi)始寫(xiě)的是class_getClassMethod(類(lèi)方法),沒(méi)有交換成功,所以一直循環(huán)調(diào)用
Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method swappedMethod = class_getInstanceMethod([NSString class], @selector(eoc_myLowercaseString));
//交換selector對(duì)應(yīng)的IMP
method_exchangeImplementations(originalMethod, swappedMethod);
NSString *lowerCase = [self eoc_myLowercaseString];
NSLog(@"%@ - > %@",self,lowerCase);
return lowerCase;
}
使用:
NSString *testString = @"This is the testString";
NSString *resultString = [testString eoc_myLowercaseString];
結(jié)果
對(duì)象
(1)OC中對(duì)象并非在編譯期就綁定好了,而是在運(yùn)行期查找的。
(2)正常情況下,應(yīng)該指明receiver的類(lèi)型,如果想它發(fā)送無(wú)法解讀的message的話,會(huì)報(bào)錯(cuò)。
(3)特殊情況id類(lèi)型的對(duì)象,id 指代任意的OC對(duì)象類(lèi)型。編譯器假定它能響應(yīng)任何message
(4)其實(shí)每個(gè)OC對(duì)象都是指向某塊內(nèi)存地址的指針。比如:
NSString *pointerVariable = @"some thing";
這里的pointerVariable可以理解為指向NSString的實(shí)例變量some thing的指針。
id genericTypedString = @"Some thing";
因?yàn)檫@里id類(lèi)型,本身就已經(jīng)是指針了,所以不用加*。
所有OC對(duì)象都是分配在堆上的,如果不使用*,那么會(huì)把對(duì)象分配在棧上,編譯器會(huì)報(bào)錯(cuò)
棧和堆的區(qū)別:< >。
(5)OC對(duì)象所用的結(jié)構(gòu)體定義在運(yùn)行期的程序庫(kù)頭文件中,比如id類(lèi)型:
typedef struct objc_object *id;
struct objc_object{
Class isa;
};
首個(gè)結(jié)構(gòu)體的首個(gè)成員是Class類(lèi)的變量,isa指針指向?qū)ο笏鶎俚念?lèi)。
Class對(duì)象也定義在運(yùn)行期程序庫(kù)的頭文件中:
typedef struct objc_class *Class;
struct objc_class {
Class isa;
Class super_class;
const char *name;
long version;
long info;
long instance_size;
struct objc_ivar_list *ivars;
struct objc_method_list **methodLists;
struct objc_cache *cache;
struct objc_protocol_list *protocols;
};
isa指針:指向類(lèi)所屬的元類(lèi)(meta),用來(lái)表述類(lèi)對(duì)象本身所具備的的元數(shù)據(jù),類(lèi)方法即存在于元類(lèi)中。
class指針:指向本類(lèi)的超類(lèi)。
name:類(lèi)名。
version:類(lèi)的版本號(hào)。
info:類(lèi)的詳情。
instance_size:該類(lèi)實(shí)例對(duì)象的大小。
ivars:指向該類(lèi)的成員變量的列表(類(lèi)的屬性列表)。
methodLists:指向該類(lèi)的實(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 不能添加屬性的原因(為什么不能添加屬性,因?yàn)閕vars指向的是屬性列表,修改ivars,并不能添加屬性?只是改變了指向地址?)。
cache:Runtime 系統(tǒng)會(huì)把被調(diào)用的方法存到 cache 中(理論上講一個(gè)方法如果被調(diào)用,那么它有可能今后還會(huì)被調(diào)用),下次查找的時(shí)候效率更高。
protocols:指向該類(lèi)的協(xié)議列表。
類(lèi)的繼承體系圖如下:

上圖實(shí)線是 super_class 指針,虛線是 isa 指針。根元類(lèi)的超類(lèi)是NSObject,而 isa 指向了自己(元類(lèi):即類(lèi)對(duì)象所屬的類(lèi)型,也就是說(shuō)所有的元類(lèi),它的類(lèi)型都是NSObject?)。NSObject 的超類(lèi)為 nil,也就是它沒(méi)有超類(lèi)。
(6)類(lèi)型查詢(xún)
isMemberOfClass:能夠判斷對(duì)象是否為某個(gè)特定類(lèi)的實(shí)例。
isKindOfClass:能判斷對(duì)象是否為某個(gè)類(lèi)或其派生類(lèi)的實(shí)例。
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict isMemberOfClass:[NSMutableDictionary class]];//yes
[dict isMemberOfClass:[NSDictionary class]];//no
[dict isKindOfClass:[NSMutableDictionary class]];//yes
[dict isKindOfClass:[NSDictionary class]];//yes
使用isa獲得實(shí)例所屬的類(lèi),然后通過(guò)super_class指針在繼承體系中查詢(xún)。因?yàn)閷?duì)象是動(dòng)態(tài)的,所以必須通過(guò)類(lèi)型查詢(xún)才能了解對(duì)象的真實(shí)類(lèi)型。
從collection中獲取的對(duì)象時(shí),通常會(huì)使用類(lèi)型查詢(xún),這些對(duì)象一般都不是強(qiáng)類(lèi)型的,通常是id,所以如果不判斷一下的話,有可能會(huì)報(bào)錯(cuò)。
如果是代理模式的話,那么用class方法查出來(lái)的類(lèi)型和isKindOfClass查詢(xún)處的結(jié)果會(huì)不一樣,前者返回的是發(fā)起代理的對(duì)象,后者返回的是接受代理的對(duì)象。要盡量使用類(lèi)型查詢(xún),這樣會(huì)得到準(zhǔn)確的對(duì)象類(lèi)型。