在調(diào)用對象的方法之前我們首先弄清楚對象的方法存在哪里.
實例方法存在類對象中
類方法存在元類對象中(元類其實也是一個類對象)
我們先看下類對象的結構布局
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}
我們看到一個類對象就是一個結構體繼承與objc_object結構體,我們以前的文章中分析過objc_object結構體,這里簡單的說一下objc_object, objc_object里面有一個isa是一個共用體.里面有一個結構體使用位域來存儲更多的信息.
superclass是指向父類的指針
cache方法的緩存列表
bits& FAST_DATA_MASK的到class_rw_t結構體
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
}
在class_rw_t中 methods就是方法的緩存類表
我們以前討論分類的的時候也分析過method_array_t methods;結構
methods是一個二維數(shù)組, 數(shù)組里面的元素是分類的的方法類表
[
[分類方法1a方法,分類方法1b方法],
[分類方法2a方法,分類方法2b方法]
]
我們給一個對象發(fā)消息的時候就會找到methods這個數(shù)組里面然后緩存到cache中,就算給一個對象調(diào)用父類的方法也會緩存到cache中的_buckets, 當往_buckets緩存bucket_t的時候_buckets會檢查是否需要擴容,需要擴容就會清空所有元素然后,在緩存進來.(這個時候以前的緩存就沒有了)
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask; //散列表的長度
mask_t _occupied; //已經(jīng)緩存方法的個數(shù)
}
struct bucket_t {
private:
cache_key_t _key;
IMP _imp;
}
我看到cache結構體中_buckets是一個數(shù)組里面是bucket_t結構體_key就是方法名字_buckets就相當于一個離散列表(類似字典)
源碼分析調(diào)用方法過程
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
LNilOrTagged:
b.eq LReturnZero // nil check
// tagged
mov x10, #0xf000000000000000
cmp x0, x10
b.hs LExtTag
adrp x10, _objc_debug_taggedpointer_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
ubfx x11, x0, #60, #4
ldr x16, [x10, x11, LSL #3]
b LGetIsaDone
LExtTag:
// ext tagged
adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
ubfx x11, x0, #52, #8
ldr x16, [x10, x11, LSL #3]
b LGetIsaDone
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
MESSENGER_END_NIL
ret
END_ENTRY _objc_msgSend
x0, 寄存器里面存的是receiver消息接受者
b.le LNilOrTagged //如果receiver為nil跳轉到LNilOrTagged
b.eq LReturnZero // nil check
ret (表示return)
如果對象為nil就return
如果對象不為nil就走到CacheLookup 在緩存中尋找
.macro CacheLookup
// x1 = SEL, x16 = isa
ldp x10, x11, [x16, #CACHE] // x10 = buckets, x11 = occupied|mask
and w12, w1, w11 // x12 = _cmd & mask
add x12, x10, x12, LSL #4 // x12 = buckets + ((_cmd & mask)<<4)
ldp x9, x17, [x12] // {x9, x17} = *bucket
1: cmp x9, x1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: x12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp x12, x10 // wrap if bucket == buckets
b.eq 3f
ldp x9, x17, [x12, #-16]! // {x9, x17} = *--bucket
b 1b // loop
3: // wrap: x12 = first bucket, w11 = mask
add x12, x12, w11, UXTW #4 // x12 = buckets+(mask<<4)
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
ldp x9, x17, [x12] // {x9, x17} = *bucket
1: cmp x9, x1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: x12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp x12, x10 // wrap if bucket == buckets
b.eq 3f
ldp x9, x17, [x12, #-16]! // {x9, x17} = *--bucket
b 1b // loop
3: // double wrap
JumpMiss $0
.endmacro
CacheHit 命中緩存 結果是直接調(diào)用方法或者返回imp指針
.macro CacheHit
.if $0 == NORMAL
MESSENGER_END_FAST
br x17 // call imp
.elseif $0 == GETIMP
mov x0, x17 // return imp
ret
.elseif $0 == LOOKUP
ret // return imp via x17
.else
.abort oops
.endif
.endmacro
CheckMiss在緩存中沒有找到
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP
cbz x9, LGetImpMiss
.elseif $0 == NORMAL
cbz x9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
cbz x9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
因為CacheLookup NORMAL傳的值是NORMAL 這里我們暫時只分析__objc_msgSend_uncached
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band x16 is the class to search
MethodTableLookup
br x17
END_ENTRY __objc_msgSend_uncached
我們再分析MethodTableLookup 發(fā)現(xiàn)是個宏
.macro MethodTableLookup
// push frame
stp fp, lr, [sp, #-16]!
mov fp, sp
// save parameter registers: x0..x8, q0..q7
sub sp, sp, #(10*8 + 8*16)
stp q0, q1, [sp, #(0*16)]
stp q2, q3, [sp, #(2*16)]
stp q4, q5, [sp, #(4*16)]
stp q6, q7, [sp, #(6*16)]
stp x0, x1, [sp, #(8*16+0*8)]
stp x2, x3, [sp, #(8*16+2*8)]
stp x4, x5, [sp, #(8*16+4*8)]
stp x6, x7, [sp, #(8*16+6*8)]
str x8, [sp, #(8*16+8*8)]
// receiver and selector already in x0 and x1
mov x2, x16
bl __class_lookupMethodAndLoadCache3
// imp in x0
mov x17, x0
// restore registers and return
ldp q0, q1, [sp, #(0*16)]
ldp q2, q3, [sp, #(2*16)]
ldp q4, q5, [sp, #(4*16)]
ldp q6, q7, [sp, #(6*16)]
ldp x0, x1, [sp, #(8*16+0*8)]
ldp x2, x3, [sp, #(8*16+2*8)]
ldp x4, x5, [sp, #(8*16+4*8)]
ldp x6, x7, [sp, #(8*16+6*8)]
ldr x8, [sp, #(8*16+8*8)]
mov sp, fp
ldp fp, lr, [sp], #16
.endmacro
bl __class_lookupMethodAndLoadCache3 意思是跳轉到__class_lookupMethodAndLoadCache3這個方法.
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
接下來就來到了lookUpImpOrForward
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// Optimistic cache lookup
if (cache) { //cache為NO在_objc_msgSend匯編代碼中已經(jīng)在cache中找過了,這里就不用再在緩存中找了
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.
// runtimeLock is held during method search to make
// method-lookup + cache-fill atomic with respect to method addition.
// Otherwise, a category could be added but ignored indefinitely because
// the cache was re-filled with the old value after the cache flush on
// behalf of the category.
runtimeLock.read();
//判斷類是否已經(jīng)實現(xiàn)過了
if (!cls->isRealized()) {
// Drop the read-lock and acquire the write-lock.
// realizeClass() checks isRealized() again to prevent
// a race while the lock is down.
runtimeLock.unlockRead();
runtimeLock.write();
realizeClass(cls);//如果類沒有實現(xiàn)過,就去實現(xiàn)類
runtimeLock.unlockWrite();
runtimeLock.read();
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
retry:
runtimeLock.assertReading();
// Try this class's cache.
imp = cache_getImp(cls, sel);//再次從緩存中檢查
if (imp) goto done;
// Try this class's method lists.從自己的類中查找方法找到就把imp返回了
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// Try superclass caches and method lists. //如果自己的的類中沒有找到從父類中找到
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;curClass != nil;curClass = curClass->superclass)
{
//curClass是父類 curClass != nil;curClass = curClass->superclass一直便利,父類
// Halt if there is a cycle in the superclass chain.如果超類鏈中存在循環(huán)裙士,則停止究珊。
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.從父類的緩存中找imp
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.在父類中的緩存中找都方法
log_and_fill_cache(cls, imp, sel, inst, curClass);//緩存到消息接受者的類中的緩存中
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
// Superclass method list. 在父類中的方法緩存中沒有找到方法,就下父類的方法列表中尋找
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// No implementation found. Try method resolver once.
//自己類的緩存,方法列表,所有父類的緩存,方法類表都沒找到 進入動態(tài)方法解析階段
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; //在動態(tài)解析后就會 返回到retry 從新走一遍消息發(fā)送流程(如果動態(tài)解析階段我們動態(tài)的添加了方法.就會找到imp 如果沒有動態(tài)解析就會走到最后一個階段消息轉發(fā))
}
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache; //消息轉發(fā)
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlockRead();
return imp;
}
我們分析一下_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);
}
}
}
如果是實例方法則會調(diào)用 _class_resolveInstanceMethod.
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));
}
}
}
我們分析一下_class_resolveInstanceMethod方法SEL_resolveInstanceMethod 是+ (BOOL)resolveInstanceMethod:(SEL)sel 通過objc_msgSend調(diào)用這個類方法, 我們可以重寫這個類方法, 并且在類方法中 動態(tài)添加方法
-(void)test1{
NSLog(@"---");
}
+(void)test1{
NSLog(@"---");
}
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(test)) {
Class class = object_getClass(self);
Method method = class_getInstanceMethod(class, @selector(test1));
IMP imp = method_getImplementation(method);
const char * types = method_getTypeEncoding(method);
class_addMethod(class, sel,imp,types);
}
return [super resolveClassMethod:sel];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(test)) {
Method method = class_getInstanceMethod(self, @selector(test1));
IMP imp = method_getImplementation(method);
const char * types = method_getTypeEncoding(method);
class_addMethod(self, sel,imp,types);
}
return [super resolveInstanceMethod:sel];
}
如果我們動態(tài)解析沒有做事情 就會來到消息轉發(fā)_objc_msgForward_impcache這個imp我們發(fā)現(xiàn)在匯編中找到,但是經(jīng)過分析是沒有源碼的,這里暫不分析匯編
下面列出動態(tài)轉發(fā)的幾個方法.
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return [LC_Tool new];
}
return [super forwardingTargetForSelector:aSelector];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(test)) {
NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:""];
return methodSignature;
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
//這里拿到 anInvocation可以為所欲為
//可以任何事情都不做
//可以轉發(fā)給一個對象調(diào)用方法
//[anInvocation invokeWithTarget:[NSObject new]];
//可以改變參數(shù)
int a = 10;
[anInvocation setArgument:&a atIndex:2]; //第0個參數(shù)是消息接受者.第1個參數(shù)是_cmd
[anInvocation invokeWithTarget:[NSObject new]];
}
在消息轉發(fā)階段如果-forwardingTargetForSelector沒有實現(xiàn),就會調(diào)用- methodSignatureForSelector方法自己返回方法簽名,
然后調(diào)用-forwardInvocation返回一個NSInvocation對象
補充一點如果 消息轉發(fā)階段這個消息是類方法就會調(diào)用+forwardingTargetForSelector,+ methodSignatureForSelector
,+ forwardInvocation (雖然沒有暴露出api)
不管是類方法還是對象方法在消息轉發(fā)階段, 其實都是消息接受者調(diào)用以上的方法.(這樣就可以理解為啥 ,對象方法調(diào)用-號類方法調(diào)用+號了 因為消息接受者不同)
objc_msgSend的執(zhí)行流程可以分為3大階段
1消息發(fā)送
2動態(tài)方法解析
3消息轉發(fā)