1.消息發(fā)送objc_msgSend
OC中在運行期決定調(diào)用什么方法,方法的調(diào)用轉換成C函數(shù)
//#import <objc/message.h>
objc_msgSend(obj, @selector(messageName:), parameter);
這個函數(shù)的參數(shù)個數(shù)可變,第一個是接收消息的對象,第二個是方法名,后面的是參數(shù),順序和轉換前的OC方法一樣.
接收到消息之后,這個函數(shù)會在類的方法列表中尋找,找不到會依據(jù)繼承體系向上尋找.找到之后,就會跳轉到方法的實現(xiàn)中.
objc_msgSend還會把匹配到的結果緩存到一個fast map,快速映射表里面,這個緩存是在類里面的,也就是說,其實objc_msgSend是先在fast map中進行匹配的,然后才是方法列表.如果消息要返回結構體,那么需要使用objc_msgSend_stret
如果消息返回浮點數(shù),那么需要使用objc_msgSend_fpret
如果要給父類發(fā)送消息.需要使用objc_msgSendSuper,另外還有objc_msgSendSuper_stret和objc_msgSendSuper_fpretOC的每一個方法都會轉換為C函數(shù),都是<return type>Class_selector(id self,SEL _cmd,...)的格式,每個類中有一張表,其中的指針會指向這些函數(shù),方法名就是key,objc_msgSend根據(jù)key找到對應函數(shù),OC有一種叫做尾調(diào)用優(yōu)化的技術,如果一個函數(shù)的結尾是調(diào)用另一個函數(shù)并且沒有返回值的時候,編譯器會生成跳轉至另一函數(shù)的指令,這個操作省去了將另一個函數(shù)推入新的棧幀,避免過早出現(xiàn)棧溢出.
2.消息轉發(fā)
前面說到消息的傳遞,如果objc_msgSend最終都沒有找到對應的函數(shù),也就是對象收到了無法解讀的消息,這時候就會啟動消息轉發(fā)機制.
分為兩大階段,第一階段,先看類能不能動態(tài)添加方法來處理這條消息,叫做動態(tài)方法解析;第二階段,第一步先找有沒有其他對象可以處理,如果有則轉發(fā)給那個對象,如果沒有則啟動完整消息轉發(fā),消息會被封裝到NSInvocation對象中,再一次詢問接受者能否處理.
- 動態(tài)方法解析
對象在接收到無法解讀的消息時,會調(diào)用+ (BOOL)resolveInstanceMethod:(SEL)sel;sel就是那個無法解讀的選擇器,返回bool類型,表示能否新增一個方法來處理這個選擇器,如果這個選擇器是一個類方法,則調(diào)用+ (BOOL)resolveClassMethod:(SEL)sel方法,這兩個方法需要在類中重寫.
為對象增加方法調(diào)用BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );
Class cls即類對象, SEL name是選擇器,也就是方法名,IMP imp是方法的具體實現(xiàn),也就是C函數(shù),const char *types是函數(shù)類型,例如"v@:"是無參無返回值(v即void),"i@:@",是有參有一個int型返回值
void newMethod(id self, SEL _cmd, id value){
NSLog(@"this is new method -- %@",value);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"undefind method");
class_addMethod(self, sel, (IMP)newMethod, "v@:@");
return YES;
}
Example *exam = [[Example alloc]init];
NSString *exstr = (NSString *)exam;
[exstr isEqualToString:@"abc"];
這是一個繼承自NSObject的類,將其對象轉換成NSString,然后調(diào)用isEqualToString方法,顯然這個類是沒有這個方法的,于是就會走resolveInstanceMethod方法,還能看到"abc"打印出來,如果調(diào)用的方法沒有參數(shù),打印出來的就是null

并且resolveInstanceMethod可以用于實現(xiàn)@dynamic的setter和getter,CALayer可以添加屬性也是這么添加屬性的
- 備用接收者,快速轉發(fā)路徑
如果沒有實現(xiàn)resolveInstanceMethod處理,運行時系統(tǒng)會尋找一個備用的接收者來處理這條消息,對應方法是- (id)forwardingTargetForSelector:(SEL)aSelector; 返回一個對象來處理消息.
- (id)forwardingTargetForSelector:(SEL)aSelector{
return @"abc";
}
Example *exam = [[Example alloc]init];
NSString *exstr = (NSString *)exam;
if([exstr isEqualToString:@"abc"]){
NSLog(@"forward target");
};

- Invocation,慢速轉發(fā)路徑
如果上面的流程任然沒有處理未知消息,系統(tǒng)會創(chuàng)建一個NSInvocation對象對應方法是- (void)forwardInvocation:(NSInvocation *)anInvocation;
運行時調(diào)用對象的方法可以使用performSelector:withObject;但是這個方法不能處理返回值,參數(shù)也最多傳2個(performSelector:<#(SEL)#> withObject:<#(id)#> withObject:<#(id)#>),NSInvocation可以將消息封裝起來,包含選擇器,目標,和參數(shù).
首先要實現(xiàn)methodSignatureForSelector,返回一個簽名,簽名指定一個選擇器,實現(xiàn)了這個方法,才會調(diào)用forwardInvocation,在forwardInvocation中,還可以修改參數(shù)等,這兩個方法的目的是修改無法響應的消息中的信息,達到可以處理的目的.
//首先要實現(xiàn)這個方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if(![self respondsToSelector:aSelector]){
//創(chuàng)建一個NSMethodSignature 綁定需要轉換的方法
return [[self class] instanceMethodSignatureForSelector:@selector(newMethod:)];
}
return nil;
}
//如果實現(xiàn)了methodSignatureForSelector 會走這個方法
- (void)forwardInvocation:(NSInvocation *)anInvocation{
//NSMethodSignature的方法要和anInvocation一致,這一步還可以修改參數(shù)
anInvocation.selector = @selector(newMethod:);
[anInvocation invoke]; // 執(zhí)行更換后的方法
}
- (void)newMethod:(NSString *)str{
NSLog(@"newMethod string = %@",str);
}
如果想要通過更換選擇器來處理未知消息,[anInvocation invoke]也可以不執(zhí)行,或者forwardInvocation里什么都不寫,相當于忽略了這條消息.但是無論forwardInvocation里寫不寫代碼,methodSignatureForSelector中一定要換掉消息的選擇器.
NSInvocation的詳細使用方法如下:
//NSInvocation;用來包裝方法和對應的對象,它可以存儲方法的名稱,對應的對象,對應的參數(shù),
/*
NSMethodSignature:簽名:再創(chuàng)建NSMethodSignature的時候,必須傳遞一個簽名對象,簽名對象的作用:用于獲取參數(shù)的個數(shù)和方法的返回值
*/
//創(chuàng)建簽名對象的時候不是使用NSMethodSignature這個類創(chuàng)建,而是方法屬于誰就用誰來創(chuàng)建,是NSObject的方法
NSMethodSignature*signature = [[self class] instanceMethodSignatureForSelector:@selector(sendMessageWithNumber:WithContent:)];
//1、創(chuàng)建NSInvocation對象
NSInvocation*invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = self;
//invocation中的方法必須和簽名中的方法一致。
invocation.selector = @selector(sendMessageWithNumber:WithContent:);
/*第一個參數(shù):需要給指定方法傳遞的值
第一個參數(shù)需要接收一個指針,也就是傳遞值的時候需要傳遞地址*/
//第二個參數(shù):需要給指定方法的第幾個參數(shù)傳值
NSString*number = @"1111";
//注意:設置參數(shù)的索引時不能從0開始,因為0已經(jīng)被self占用,1已經(jīng)被_cmd占用
[invocation setArgument:&number atIndex:2];
NSString*number2 = @"啊啊啊";
[invocation setArgument:&number2 atIndex:3];
//2、調(diào)用NSInvocation對象的invoke方法
//只要調(diào)用invocation的invoke方法,就代表需要執(zhí)行NSInvocation對象中指定對象的指定方法,并且傳遞指定的參數(shù)
[invocation invoke];

在這個流程中,resolveInstanceMethod返回YES也不一定會結束,關鍵看這個方法中,是否添加了函數(shù)去處理這個消息,如果什么都不寫,直接reture YES也一樣會執(zhí)行后續(xù)的流程.
3.交換調(diào)配method swizzling
- 類的方法列表會把選擇器的名稱,也就是方法名映射到方法實現(xiàn)上,這些方法均以函數(shù)指針的形式來表示,這種指針叫做IMP.因此我們可以在運行時去修改選擇器對應的函數(shù)指針,可以新增選擇器,可以修改方法實現(xiàn),可以互換方法實現(xiàn).
//函數(shù)指針
id (*IMP)(id,SEL,...)
/*互換方法實現(xiàn)*/
//獲取方法實現(xiàn)
Method method1 = class_getInstanceMethod(NSString.class, @selector(ex_uppercaseString));
Method method2 = class_getInstanceMethod(NSString.class, @selector(uppercaseString));
//交換方法實現(xiàn)
method_exchangeImplementations(method1, method2);
//擴展方法,寫在分類中
- (NSString *)ex_uppercaseString{
NSString *str = [self ex_uppercaseString];
/*
...
*/
NSLog(@"擴展方法--%@",str);
return str;
}
這個方法可以將那些看不到源碼的黑盒方法進行一些擴展,例如增加日志,有助于調(diào)試.