筆者整理了一系列有關OC的底層文章,但願能夠幫助到你。html
1.iOS的OC對象建立的alloc原理c++
2.iOS的OC對象的內存對齊緩存
3.iOS的OC的isa的底層原理bash
4.iOS的OC源碼分析之類的結構分析app
5.iOS的OC的方法緩存的源碼分析ide
6.iOS的OC的方法的查找原理函數
OC的方法的查找是經過消息的發送
來查找函數的IMP
,首先經過objc_msgSend
來進行慢速查找(cache_t
),若是慢速找不到,就須要進行方法的快速查找,具體能夠了解iOS的OC的方法的查找原理這篇文章。可是,若是經過慢速和快速的查找都找不到的話,就會直接報錯。是否是說若是找不到就沒有辦法走其餘的操做了呢?並非的,接下來這邊文章就是介紹方法的決議和消息轉發原理。爲了接下來的內容介紹定義一個TestObject
類。源碼分析
實現下面的代碼,其中testErrorMthod
方法是沒有在TestObject
類聲明和實現的,直接運行是報錯的。post
TestObject *testObject = [[TestObject alloc] init];
[testObject performSelector:@selector(testErrorMthod)];
==========運行結果=================
LGTest[1639:40195] -[TestObject testErrorMthod]: unrecognized selector sent to instance 0x1010c3600
LGTest[1639:40195] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TestObject testErrorMthod]: unrecognized selector sent to instance 0x1010c3600'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff3c7438ab __exceptionPreprocess + 250
1 libobjc.A.dylib 0x000000010038dfea objc_exception_throw + 42
2 CoreFoundation 0x00007fff3c7c2b61 -[NSObject(NSObject) __retain_OA] + 0
3 CoreFoundation 0x00007fff3c6a7adf ___forwarding___ + 1427
4 CoreFoundation 0x00007fff3c6a74b8 _CF_forwarding_prep_0 + 120
5 libobjc.A.dylib 0x00000001003ccf26 -[NSObject performSelector:] + 70
6 LGTest 0x0000000100001afd main + 93
7 libdyld.dylib 0x00007fff73d6b7fd start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
複製代碼
在以前介紹方法的快速查找流程中的lookUpImpOrForward
函數中,有這段源碼,在方法查找不到的時候,會執行到裏面去。ui
// No implementation found. Try method resolver once.
if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst);
runtimeLock.lock();
// 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
的源碼,其中cls
是類,sel
方法的編號,inst
是實例對象。
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);
}
}
}
複製代碼
這段源碼對傳進來的cls
判斷是不是元類
,經過以前的文章能夠知道類方法
是存在元類
中的。因此若是傳進來的cls
是類,就直接執行_class_resolveInstanceMethod
函數,若是是元類
執行_class_resolveClassMethod
函數。
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveInstanceMethod adds to self a.k.a. cls IMP imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); if (resolved && PrintResolving) { if (imp) { _objc_inform("RESOLVE: method %c[%s %s] " "dynamically resolved to %p", cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel), imp); } else { // Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
複製代碼
其中lookUpImpOrNil
函數對類方法SEL_resolveInstanceMethod
查找是否有存在,發現SEL_resolveInstanceMethod
的實現是resolveInstanceMethod
方法,這個是在NSObject
類裏面有實現的,因此這個是返回true
的。
objc_msgSend
查找當前的類是否執行
resolveInstanceMethod
方法。
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveInstanceMethod adds to self a.k.a. cls IMP imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); 複製代碼
經過前面的文章,能夠知道若是當前的類沒有實現resolveInstanceMethod
類方法就會查找父類是否實現,由於在NSObject
類中是默認實現返回NO
的,若是當前的類有實現resolveInstanceMethod
類方法就會執行方法裏的內容。而且在實現的resolveInstanceMethod
類方法中對沒有找到的sel
從新賦值一個IMP
,下面是在TestObject
類的實現代碼。
-(void)testOk{
NSLog(@"%p===testOk",__func__);
}
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"===執行resolveInstanceMethod===%s===%@",__func__,NSStringFromSelector(sel));
if(sel == @selector(testErrorMthod)){
Method okMethod = class_getInstanceMethod(self, @selector(testOk));
IMP okImp = method_getImplementation(okMethod);
const char *type = method_getTypeEncoding(okMethod);
return class_addMethod(self, sel, okImp, type);
}
return [super resolveInstanceMethod:sel];
}
======執行的結果=======
LGTest[2769:92089] ===執行resolveInstanceMethod===+[TestObject resolveInstanceMethod:]===testErrorMthod
LGTest[2769:92089] 0x100001f2a===testOk
Program ended with exit code: 0
複製代碼
此時還執行一次lookUpImpOrNil
函數在緩存中查找。因此在TestObject
類中定義了這個方法而且爲testErrorMthod
賦值了新的IMP
。
// Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveInstanceMethod adds to self a.k.a. cls IMP imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); 複製代碼
上面的是對實例方法的動態決議,其實對類方法的也是差很少的,若是是類方法此時會執行以下源碼
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
複製代碼
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
assert(cls->isMetaClass());
if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_resolveClassMethod, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveClassMethod adds to self->ISA() a.k.a. cls IMP imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); if (resolved && PrintResolving) { if (imp) { _objc_inform("RESOLVE: method %c[%s %s] " "dynamically resolved to %p", cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel), imp); } else { // Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
複製代碼
經過源碼能夠知道是與_class_resolveInstanceMethod
源碼是差很少的,主要區別是,這個源碼的SEL_resolveClassMethod
是要實現resolveClassMethod
這個類方法,須要在TestObject
類中實現類方法resolveClassMethod
並在這個方法裏面實現邏輯。可是爲何執行完_class_resolveClassMethod
函數以後還會再作一次lookUpImpOrNil
函數的判斷呢?由於若是在_class_resolveClassMethod
是沒有作處理的,因爲元類
的查找方法查找流程是會往根元類
查找最終會找到NSObject
這個類,因此若是在根元類
都找不到的狀況下會找到NSObject
類的方法裏面。而NSObject
類是有默認實現了這兩個類方法的而且默認返回NO
。
若是對不存在的方法的查找,沒有實現上面的方法決議
,此時會在lookUpImpOrForward
函數中執行
// No implementation found, and method resolver didn't help. // Use forwarding. imp = (IMP)_objc_msgForward_impcache; cache_fill(cls, sel, imp, inst); 複製代碼
其中_objc_msgForward_impcache
就會執行到彙編中的內容,而後執行__objc_msgForward
,最終會執行到_objc_forward_handler
函數中,最終是會報錯的。
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
// Default forward handler halts the process.
__attribute__((noreturn)) void
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;
複製代碼
這些流程就是在以前文章的方法的查找原理有介紹。是否是就是說消息的轉發流程就跟宗不了呢?並非的。在lookUpImpOrForward
函數中有一個能夠打印log
的方法log_and_fill_cache
中的logMessageSend
方法裏面有介紹能夠根據objcMsgLogEnabled
屬性來控制打印log
void instrumentObjcMessageSends(BOOL flag)
{
bool enable = flag;
// Shortcut NOP
if (objcMsgLogEnabled == enable)
return;
// If enabling, flush all method caches so we get some traces
if (enable)
_objc_flush_caches(Nil);
// Sync our log file
if (objcMsgLogFD != -1)
fsync (objcMsgLogFD);
objcMsgLogEnabled = enable;
}
複製代碼
爲了能夠看到消息轉發的過程當中實現了那些方法,能夠在mac的項目中實現
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
TestObject *test = [TestObject alloc] ;
instrumentObjcMessageSends(true);
[test performSelector:@selector(testErrorMthod)];
instrumentObjcMessageSends(false);
}
return 0;
}
複製代碼
就能夠在路徑:/tmp/msgSends-
找到生成的msgSends
文件
msgSends-7265
文件的內容
這個打印log是要在mac的項目下才能夠,若是在其餘的項目下是會報objc[6984]: lock 0x100cbf0c0 (runtimeLock) acquired before 0x100cbf040 (objcMsgLogLock) with no defined lock order這種錯誤。
從打印出來的方法能夠知道,在消息的轉發的過程當中執行的過程是resolveInstanceMethod
-->forwardingTargetForSelector
-->methodSignatureForSelector
-->resolveInstanceMethod
-->doesNotRecognizeSelector
。因此若是不執行resolveInstanceMethod
方法決議,會執行forwardingTargetForSelector
方法。
在objc
的源碼中能夠找到在NSObject.mm
文件中有定義和實現forwardingTargetForSelector
的實例方法和類方法。
+ (id)forwardingTargetForSelector:(SEL)sel {
return nil;
}
- (id)forwardingTargetForSelector:(SEL)sel {
return nil;
}
複製代碼
在官方的文檔中有對forwardingTargetForSelector
方法的介紹
forwardingTargetForSelector
主要是返回一個不是自身(若是是self會進入死循壞)的對象去處理
sel
這個當前類沒法處理的消息,其餘的狀況能夠調用
super
方法。若是處理不了,會轉到效率低下的
forwardInvocation
。在效率方面,
forwardingTargetForSelector
領先
forwardInvocation
一個數量級,所以,若是能夠的話最好避免使用後者來作消息轉發。下面在
TestObject
類中添加多一個
TestForwardObject
類,而且在
TestObject
類中實現
forwardingTargetForSelector
方法。
@interface TestForwardObject : NSObject
@end
@implementation TestForwardObject
-(void)testErrorMthod{
NSLog(@"TestForwardObject的testErrorMthod方法%p",__func__);
}
@end
//在TestObject類中實現的方法
-(id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"方法名字:%@",NSStringFromSelector(aSelector));
if(aSelector == @selector(testErrorMthod)){
return [TestForwardObject alloc];
}
return [super forwardingTargetForSelector:aSelector];
}
//=========運行的結果========
LGTest[1308:29082] 方法名字:testErrorMthod
LGTest[1308:29082] TestForwardObject的testErrorMthod方法0x100001eb4
複製代碼
從中能夠看到消息的轉發到TestForwardObject
的testErrorMthod
方法執行了。可是須要注意的是轉發到其餘的類執行的方法必需要和被調用的方法相同方法簽名的方法(方法名、參數列表、返回值類型都必須一致)。不然的話,仍是報錯的。
若是在消息轉發的慢速流程中不作處理,此時會執行到消息轉發的慢速流程中,須要分別執行兩個方法分別是methodSignatureForSelector
和forwardInvocation
。
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"方法名字:%@",NSStringFromSelector(aSelector));
if(aSelector == @selector(testErrorMthod)){
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"執行forwardInvocation:%s",__func__);
}
//======運行結果==========
LGTest[1222:21266] 方法名字:testErrorMthod
LGTest[1222:21266] 執行forwardInvocation:-[TestObject forwardInvocation:]
複製代碼
在這個流程中methodSignatureForSelector
是返回的方法的簽名,能夠參考 蘋果官方類型編碼。能夠發如今forwardInvocation
方法中就算不作處理也不會奔潰,由於每一個方法其實就是一個事務,不作處理就會失效,在forwardInvocation
中作處理的話,能夠以下:
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"執行forwardInvocation:%s",__func__);
SEL inVocationSeletor = [anInvocation selector];
if([[TestForwardObject alloc] respondsToSelector:inVocationSeletor]){
[anInvocation invokeWithTarget:[TestForwardObject alloc]];
}else{
[super forwardInvocation:anInvocation];
}
}
//=====運行結果===========
LGTest[1465:30634] 方法名字:testErrorMthod
LGTest[1465:30634] 執行forwardInvocation:-[TestObject forwardInvocation:]
LGTest[1465:30634] TestForwardObject的testErrorMthod方法-[TestForwardObject testErrorMthod]
複製代碼
OC方法調用是經過objc_msgSend
先經過cache_t
的快速查找,若是找不到就要進行慢速查找
。若是都查找不到方法,就會進入方法的決議
和消息轉發流程
。若是查找的類有實現resolveInstanceMethod
或resolveClassMethod
方法對須要查找的方法作處理就完成,不然就進入消息轉發
流程。消息轉發的流程中先進入消息快速轉發流程
,須要實現forwardingTargetForSelector
方法。不然進入消息慢速轉發流程
,須要實現methodSignatureForSelector
和forwardInvocation
方法。若是都沒有,此時程序只能報錯了。最後附上消息轉發的流程圖