本文咱們探尋方法調用的本質,首先經過一段代碼,將方法調用代碼轉爲c++代碼查看方法調用的本質是什麼樣的。 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
c++
[person test];
// --------- c++底層代碼
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("test"));
複製代碼
經過上述源碼能夠看出c++底層代碼中方法調用其實都是轉化爲 objc_msgSend
函數,OC的方法調用也叫消息機制,表示給方法調用者發送消息。數組
拿上述代碼舉例,上述代碼中實際爲給person實例對象發送一條test消息。 消息接受者:person
消息名稱:test緩存
在方法調用的過程當中能夠分爲三個階段。bash
消息發送階段:負責從類及父類的緩存列表及方法列表查找方法。 動態解析階段:若是消息發送階段沒有找到方法,則會進入動態解析階段,負責動態的添加方法實現。 消息轉發階段:若是也沒有實現動態解析方法,則會進行消息轉發階段,將消息轉發給能夠處理消息的接受者來處理。iphone
若是消息轉發也沒有實現,就會報方法找不到的錯誤,沒法識別消息,unrecognzied selector sent to instance
函數
接下來咱們經過源碼探尋消息發送者三個階段分別是如何實現的。post
在runtime源碼中搜索_objc_msgSend
查看其內部實現,在objc-msg-arm64.s
彙編文件能夠知道_objc_msgSend
函數的實現學習
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
MESSENGER_START
cmp x0, #0 // nil check and tagged pointer check
b.le LNilOrTagged // (MSB tagged pointer looks negative)
ldr x13, [x0] // x13 = isa
and x16, x13, #ISA_MASK // x16 = class
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
複製代碼
上述彙編源碼中會首先判斷消息接受者reveiver
的值。 若是傳入的消息接受者爲nil則會執行LNilOrTagged
,LNilOrTagged
內部會執行LReturnZero
,而LReturnZero
內部則直接return0。ui
若是傳入的消息接受者不爲nill則執行CacheLookup
,內部對方法緩存列表進行查找,若是找到則執行CacheHit
,進而調用方法。不然執行CheckMiss
,CheckMiss
內部調用__objc_msgSend_uncached
。spa
__objc_msgSend_uncached
內會執行MethodTableLookup
也就是方法列表查找,MethodTableLookup
內部的核心代碼__class_lookupMethodAndLoadCache3
也就是c語言函數_class_lookupMethodAndLoadCache3
c語言_class_lookupMethodAndLoadCache3
函數內部則是對方法查找的核心源代碼。
首先經過一張圖看一下彙編語言中_objc_msgSend的運行流程。
方法查找的核心函數就是_class_lookupMethodAndLoadCache3
函數,接下來重點分析_class_lookupMethodAndLoadCache3
函數內的源碼。
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
複製代碼
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
// initialize = YES , cache = NO , resolver = YES
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// 緩存查找, 由於cache傳入的爲NO, 這裏不會進行緩存查找, 由於在彙編語言中CacheLookup已經查找過
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
runtimeLock.read();
if (!cls->isRealized()) {
runtimeLock.unlockRead();
runtimeLock.write();
realizeClass(cls);
runtimeLock.unlockWrite();
runtimeLock.read();
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
}
retry:
runtimeLock.assertReading();
// 防止動態添加方法,緩存會變化,再次查找緩存。
imp = cache_getImp(cls, sel);
// 若是查找到imp, 直接調用done, 返回方法地址
if (imp) goto done;
// 查找方法列表, 傳入類對象和方法名
{
// 根據sel去類對象裏面查找方法
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
// 若是方法存在,則緩存方法,
// 內部調用的就是 cache_fill 上文中已經詳細講解過這個方法,這裏不在贅述了。
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
// 方法緩存以後, 取出imp, 調用done返回imp
imp = meth->imp;
goto done;
}
}
// 若是類方法列表中沒有找到, 則去父類的緩存中或方法列表中查找方法
{
unsigned attempts = unreasonableClassCount();
// 若是父類緩存列表及方法列表均找不到方法,則去父類的父類去查找。
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// 查找父類的緩存
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// 在父類中找到方法, 在本類中緩存方法, 注意這裏傳入的是cls, 將方法緩存在本類緩存列表中, 而非父類中
log_and_fill_cache(cls, imp, sel, inst, curClass);
// 執行done, 返回imp
goto done;
}
else {
// 跳出循環, 中止搜索
break;
}
}
// 查找父類的方法列表
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
// 一樣拿到方法, 在本類進行緩存
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
// 執行done, 返回imp
goto done;
}
}
}
// ---------------- 消息發送階段完成 ---------------------
// ---------------- 進入動態解析階段 ---------------------
// 上述列表中都沒有找到方法實現, 則嘗試解析方法
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
triedResolver = YES;
goto retry;
}
// ---------------- 動態解析階段完成 ---------------------
// ---------------- 進入消息轉發階段 ---------------------
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlockRead();
// 返回方法地址
return imp;
}
複製代碼
方法列表中查找方法
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
assert(cls->isRealized());
// cls->data() 獲得的是 class_rw_t
// class_rw_t->methods 獲得的是methods二維數組
for (auto mlists = cls->data()->methods.beginLists(),
end = cls->data()->methods.endLists();
mlists != end;
++mlists)
{
// mlists 爲 method_list_t
method_t *m = search_method_list(*mlists, sel);
if (m) return m;
}
return nil;
}
複製代碼
上述源碼中getMethodNoSuper_nolock
函數中經過遍歷方法列表拿到method_list_t
最終經過search_method_list
函數查找方法
search_method_list
函數
static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
// 若是方法列表是有序的,則使用二分法查找方法,節省時間
if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
return findMethodInSortedMethodList(sel, mlist);
} else {
// 不然則遍歷列表查找
for (auto& meth : *mlist) {
if (meth.name == sel) return &meth;
}
}
return nil;
}
複製代碼
findMethodInSortedMethodList
函數內二分查找實現原理
static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
assert(list);
const method_t * const first = &list->first;
const method_t *base = first;
const method_t *probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
// >>1 表示將變量n的各個二進制位順序右移1位,最高位補二進制0。
// count >>= 1 若是count爲偶數則值變爲(count / 2)。若是count爲奇數則值變爲(count-1) / 2
for (count = list->count; count != 0; count >>= 1) {
// probe 指向數組中間的值
probe = base + (count >> 1);
// 取出中間method_t的name,也就是SEL
uintptr_t probeValue = (uintptr_t)probe->name;
if (keyValue == probeValue) {
// 取出 probe
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
probe--;
}
// 返回方法
return (method_t *)probe;
}
// 若是keyValue > probeValue 則折半向後查詢
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
複製代碼
至此爲止,消息發送階段已經完成。 咱們經過一站圖來看一下_class_lookupMethodAndLoadCache3
函數內部消息發送的整個流程
若是消息發送階段沒有找到方法,就會進入動態解析方法階段。
當本類包括父類cache
包括class_rw_t
中都找不到方法時,就會進入動態方法解析階段。咱們來看一下動態解析階段源碼。
動態解析的方法
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
複製代碼
_class_resolveMethod
函數內部,根據類對象或元類對象作不一樣的操做
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
複製代碼
上述代碼中能夠發現,動態解析方法以後,會將triedResolver = YES;
那麼下次就不會在進行動態解析階段了,以後會從新執行retry
,會從新對方法查找一遍。也就是說不管咱們是否實現動態解析方法,不管動態解析方法是否成功,retry
以後都不會在進行動態的解析方法了。
動態解析對象方法時,會調用+(BOOL)resolveInstanceMethod:(SEL)sel
方法。 動態解析類方法時,會調用+(BOOL)resolveClassMethod:(SEL)sel
方法。
這裏以實例對象爲例經過代碼來看一下動態解析的過程
@implementation Person
- (void) other {
NSLog(@"%s", __func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
// 動態的添加方法實現
if (sel == @selector(test)) {
// 獲取其餘方法 指向method_t的指針
Method otherMethod = class_getInstanceMethod(self, @selector(other));
// 動態添加test方法的實現
class_addMethod(self, sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
// 返回YES表示有動態添加方法
return YES;
}
NSLog(@"%s", __func__);
return [super resolveInstanceMethod:sel];
}
@end
複製代碼
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person test];
}
return 0;
}
// 打印結果
// -[Person other]
複製代碼
上述代碼中能夠看出,person
在調用test
方法時通過動態解析成功調用了other
方法。
經過上面對消息發送的分析咱們知道,當本類和父類cache
和class_rw_t
中都找不到方法時,就會進行動態解析的方法,也就是說會自動調用類的resolveInstanceMethod:
方法進行動態查找。所以咱們能夠在resolveInstanceMethod:
方法內部使用class_addMethod
動態的添加方法實現。
這裏須要注意class_addMethod
用來向具備給定名稱和實現的類添加新方法,class_addMethod
將添加一個方法實現的覆蓋,可是不會替換已有的實現。也就是說若是上述代碼中已經實現了-(void)test
方法,則不會再動態添加方法,這點在上述源碼中也能夠體現,由於一旦找到方法實現就直接return imp並調用方法了,不會再執行動態解析方法了。
咱們來看一下class_addMethod
函數的參數分別表明什麼。
/**
第一個參數: cls:給哪一個類添加方法
第二個參數: SEL name:添加方法的名稱
第三個參數: IMP imp: 方法的實現,函數入口,函數名可與方法名不一樣(建議與方法名相同)
第四個參數: types :方法類型,須要用特定符號,參考API
*/
class_addMethod(__unsafe_unretained Class cls, SEL name, IMP imp, const char *types)
複製代碼
上述參數上文中已經詳細講解過,這裏再也不贅述。
須要注意的是咱們在上述代碼中經過class_getInstanceMethod
獲取Method
的方法
// 獲取其餘方法 指向method_t的指針
Method otherMethod = class_getInstanceMethod(self, @selector(other));
複製代碼
其實Method是objc_method
類型結構體,能夠理解爲其內部結構同method_t
結構體相同,上文中提到過method_t
是表明方法的結構體,其內部包含SEL、type、IMP
,咱們經過自定義method_t
結構體,將objc_method
強轉爲method_t
查看方法是否可以動態添加成功。
struct method_t {
SEL sel;
char *types;
IMP imp;
};
- (void) other {
NSLog(@"%s", __func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
// 動態的添加方法實現
if (sel == @selector(test)) {
// Method強轉爲method_t
struct method_t *method = (struct method_t *)class_getInstanceMethod(self, @selector(other));
NSLog(@"%s,%p,%s",method->sel,method->imp,method->types);
// 動態添加test方法的實現
class_addMethod(self, sel, method->imp, method->types);
// 返回YES表示有動態添加方法
return YES;
}
NSLog(@"%s", __func__);
return [super resolveInstanceMethod:sel];
}
複製代碼
查看打印內容
動態解析方法[3246:1433553] other,0x100000d00,v16@0:8
動態解析方法[3246:1433553] -[Person other]
複製代碼
能夠看出確實能夠打印出相關信息,那麼咱們就能夠理解爲objc_method
內部結構同method_t
結構體相同,能夠表明類定義中的方法。
另外上述代碼中咱們經過method_getImplementation
函數和method_getTypeEncoding
函數獲取方法的imp
和type
。固然咱們也能夠經過本身寫的方式來調用,這裏以動態添加有參數的方法爲例。
+(BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(eat:)) {
class_addMethod(self, sel, (IMP)cook, "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void cook(id self ,SEL _cmd,id Num)
{
// 實現內容
NSLog(@"%@的%@方法動態實現了,參數爲%@",self,NSStringFromSelector(_cmd),Num);
}
複製代碼
上述代碼中當調用eat:
方法時,動態添加了cook
函數做爲其實現並添加id類型的參數。
當動態解析類方法的時候,就會調用+(BOOL)resolveClassMethod:(SEL)sel
函數,而咱們知道類方法是存儲在元類對象裏面的,所以cls第一個對象須要傳入元類對象如下代碼爲例
void other(id self, SEL _cmd)
{
NSLog(@"other - %@ - %@", self, NSStringFromSelector(_cmd));
}
+ (BOOL)resolveClassMethod:(SEL)sel
{
if (sel == @selector(test)) {
// 第一個參數是object_getClass(self),傳入元類對象。
class_addMethod(object_getClass(self), sel, (IMP)other, "v16@0:8");
return YES;
}
return [super resolveClassMethod:sel];
}
複製代碼
咱們在上述源碼的分析中提到過,不管咱們是否實現了動態解析的方法,系統內部都會執行retry
對方法再次進行查找,那麼若是咱們實現了動態解析方法,此時就會順利查找到方法,進而返回imp
對方法進行調用。若是咱們沒有實現動態解析方法。就會進行消息轉發。
接下來看一下動態解析方法流程圖示
若是咱們本身也沒有對方法進行動態的解析,那麼就會進行消息轉發
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
複製代碼
本身沒有能力處理這個消息的時候,就會進行消息轉發階段,會調用_objc_msgForward_impcache
函數。
經過搜索能夠在彙編中找到__objc_msgForward_impcache
函數實現,__objc_msgForward_impcache
函數中調用__objc_msgForward
進而找到__objc_forward_handler
。
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
複製代碼
咱們發現這僅僅是一個錯誤信息的輸出。 其實消息轉發機制是不開源的,可是咱們能夠猜想其中可能拿返回的對象調用了objc_msgSend
,重走了一遍消息發送,動態解析,消息轉發的過程。最終找到方法進行調用。
咱們經過代碼來看一下,首先建立Car
類繼承自NSObject
,而且Car
有一個- (void) driving
方法,當Person類實例對象
失去了駕車的能力,而且沒有在開車過程當中動態的學會駕車,那麼此時就會將開車這條信息轉發給Car
,由Car實例對象
來幫助person對象
駕車。
#import "Car.h"
@implementation Car
- (void) driving
{
NSLog(@"car driving");
}
@end
--------------
#import "Person.h"
#import <objc/runtime.h>
#import "Car.h"
@implementation Person
- (id)forwardingTargetForSelector:(SEL)aSelector
{
// 返回可以處理消息的對象
if (aSelector == @selector(driving)) {
return [[Car alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
--------------
#import<Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person driving];
}
return 0;
}
// 打印內容
// 消息轉發[3452:1639178] car driving
複製代碼
由上述代碼能夠看出,當本類沒有實現方法,而且沒有動態解析方法,就會調用forwardingTargetForSelector
函數,進行消息轉發,咱們能夠實現forwardingTargetForSelector
函數,在其內部將消息轉發給能夠實現此方法的對象。
若是forwardingTargetForSelector
函數返回爲nil
或者沒有實現的話,就會調用methodSignatureForSelector
方法,用來返回一個方法簽名,這也是咱們正確跳轉方法的最後機會。
若是methodSignatureForSelector
方法返回正確的方法簽名就會調用forwardInvocation
方法,forwardInvocation
方法內提供一個NSInvocation
類型的參數,NSInvocation
封裝了一個方法的調用,包括方法的調用者,方法名,以及方法的參數。在forwardInvocation
函數內修改方法調用對象便可。
若是methodSignatureForSelector
返回的爲nil,就會來到doseNotRecognizeSelector:
方法內部,程序crash提示沒法識別選擇器unrecognized selector sent to instance
。
咱們經過如下代碼進行驗證
- (id)forwardingTargetForSelector:(SEL)aSelector
{
// 返回可以處理消息的對象
if (aSelector == @selector(driving)) {
// 返回nil則會調用methodSignatureForSelector方法
return nil;
// return [[Car alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
// 方法簽名:返回值類型、參數類型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(driving)) {
// return [NSMethodSignature signatureWithObjCTypes: "v@:"];
// return [NSMethodSignature signatureWithObjCTypes: "v16@0:8"];
// 也能夠經過調用Car的methodSignatureForSelector方法獲得方法簽名,這種方式須要car對象有aSelector方法
return [[[Car alloc] init] methodSignatureForSelector: aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
//NSInvocation 封裝了一個方法調用,包括:方法調用者,方法,方法的參數
// anInvocation.target 方法調用者
// anInvocation.selector 方法名
// [anInvocation getArgument: NULL atIndex: 0]; 得到參數
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
// anInvocation中封裝了methodSignatureForSelector函數中返回的方法。
// 此時anInvocation.target 仍是person對象,咱們須要修改target爲能夠執行方法的方法調用者。
// anInvocation.target = [[Car alloc] init];
// [anInvocation invoke];
[anInvocation invokeWithTarget: [[Car alloc] init]];
}
// 打印內容
// 消息轉發[5781:2164454] car driving
複製代碼
上述代碼中能夠發現方法能夠正常調用。接下來咱們來看一下消息轉發階段的流程圖
methodSignatureForSelector
方法中返回的方法簽名,在forwardInvocation
中被包裝成NSInvocation
對象,NSInvocation
提供了獲取和修改方法名、參數、返回值等方法,也就是說,在forwardInvocation
函數中咱們能夠對方法進行最後的修改。
一樣上述代碼,咱們爲driving方法添加返回值和參數,並在forwardInvocation
方法中修改方法的返回值及參數。
#import "Car.h"
@implementation Car
- (int) driving:(int)time
{
NSLog(@"car driving %d",time);
return time * 2;
}
@end
#import "Person.h"
#import <objc/runtime.h>
#import "Car.h"
@implementation Person
- (id)forwardingTargetForSelector:(SEL)aSelector
{
// 返回可以處理消息的對象
if (aSelector == @selector(driving)) {
return nil;
}
return [super forwardingTargetForSelector:aSelector];
}
// 方法簽名:返回值類型、參數類型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(driving:)) {
// 添加一個int參數及int返回值type爲 i@:i
return [NSMethodSignature signatureWithObjCTypes: "i@:i"];
}
return [super methodSignatureForSelector:aSelector];
}
//NSInvocation 封裝了一個方法調用,包括:方法調用者,方法,方法的參數
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
int time;
// 獲取方法的參數,方法默認還有self和cmd兩個參數,所以新添加的參數下標爲2
[anInvocation getArgument: &time atIndex: 2];
NSLog(@"修改前參數的值 = %d",time);
time = time + 10; // time = 110
NSLog(@"修改前參數的值 = %d",time);
// 設置方法的參數 此時將參數設置爲110
[anInvocation setArgument: &time atIndex:2];
// 將tagert設置爲Car實例對象
[anInvocation invokeWithTarget: [[Car alloc] init]];
// 獲取方法的返回值
int result;
[anInvocation getReturnValue: &result];
NSLog(@"獲取方法的返回值 = %d",result); // result = 220,說明參數修改爲功
result = 99;
// 設置方法的返回值 從新將返回值設置爲99
[anInvocation setReturnValue: &result];
// 獲取方法的返回值
[anInvocation getReturnValue: &result];
NSLog(@"修改方法的返回值爲 = %d",result); // result = 99
}
#import<Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
// 傳入100,並打印返回值
NSLog(@"[person driving: 100] = %d",[person driving: 100]);
}
return 0;
}
複製代碼
消息轉發[6415:2290423] 修改前參數的值 = 100
消息轉發[6415:2290423] 修改前參數的值 = 110
消息轉發[6415:2290423] car driving 110
消息轉發[6415:2290423] 獲取方法的返回值 = 220
消息轉發[6415:2290423] 修改方法的返回值爲 = 99
消息轉發[6415:2290423] [person driving: 100] = 99
複製代碼
從上述打印結果能夠看出forwardInvocation
方法中能夠對方法的參數及返回值進行修改。
而且咱們能夠發現,在設置tagert爲Car實例對象時,就已經對方法進行了調用,而在forwardInvocation
方法結束以後才輸出返回值。
經過上述驗證咱們能夠知道只要來到forwardInvocation
方法中,咱們便對方法調用有了絕對的掌控權,能夠選擇是否調用方法,以及修改方法的參數返回值等等。
類方法消息轉發同對象方法同樣,一樣須要通過消息發送,動態方法解析以後纔會進行消息轉發機制。咱們知道類方法是存儲在元類對象中的,元類對象原本也是一種特殊的類對象。須要注意的是,類方法的消息接受者變爲類對象。
當類對象進行消息轉發時,對調用相應的+號的forwardingTargetForSelector、methodSignatureForSelector、forwardInvocation
方法,須要注意的是+號方法僅僅沒有提示,而不是系統不會對類方法進行消息轉發。
下面經過一段代碼查看類方法的消息轉發機制。
int main(int argc, const char * argv[]) {
@autoreleasepool {
[Person driving];
}
return 0;
}
#import "Car.h"
@implementation Car
+ (void) driving;
{
NSLog(@"car driving");
}
@end
#import "Person.h"
#import <objc/runtime.h>
#import "Car.h"
@implementation Person
+ (id)forwardingTargetForSelector:(SEL)aSelector
{
// 返回可以處理消息的對象
if (aSelector == @selector(driving)) {
// 這裏須要返回類對象
return [Car class];
}
return [super forwardingTargetForSelector:aSelector];
}
// 若是forwardInvocation函數中返回nil 則執行下列代碼
// 方法簽名:返回值類型、參數類型
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(driving)) {
return [NSMethodSignature signatureWithObjCTypes: "v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation
{
[anInvocation invokeWithTarget: [Car class]];
}
// 打印結果
// 消息轉發[6935:2415131] car driving
複製代碼
上述代碼中一樣能夠對類對象方法進行消息轉發。須要注意的是類方法的接受者爲類對象。其餘同對象方法消息轉發模式相同。
OC中的方法調用其實都是轉成了objc_msgSend
函數的調用,給receiver(方法調用者)發送了一條消息(selector方法名)。方法調用過程當中也就是objc_msgSend
底層實現分爲三個階段:消息發送、動態方法解析、消息轉發。本文主要對這三個階段相互之間的關係以及流程進行的探索。上文中已經講解的很詳細,這裏再也不贅述。
文中若是有不對的地方歡迎指出。我是xx_cc,一隻長大好久但尚未二夠的傢伙。須要視頻一塊兒探討學習的coder能夠加我Q:2336684744