上篇咱們講過了iOS底層原理 runtime-object_class拾遺基礎,arm64以後isa是使用聯合體使用更少的空間存儲更多的數據,以及如何自定義和使用聯合體,objc_class->cache_t cache
是一個是緩存最近調用class
的方法,當緩存剩餘空間小余1/4則進行擴容,擴容爲原來的兩倍,擴容以後,已存儲的method_t
擴容以後以後被清空。今天咱們在瞭解runtime的消息轉發機制。c++
OC中的方法調用,其實都是轉換爲objc_msgSend函數的調用git
objc_msgSend的執行流程能夠分爲3大階段github
那麼咱們根據這三塊內容進行源碼解讀。源碼執行的順序大概以下數組
objc-msg-arm64.s
ENTRY _objc_msgSend
b.le LNilOrTagged //<0則返回
CacheLookup NORMAL //緩存查找 未命中則繼續查找
.macro CacheLookup// 經過宏 查找cache,命中直接call or return imp
.macro CheckMiss //miss 則跳轉__objc_msgSend_uncached
STATIC_ENTRY __objc_msgSend_uncached
.macro MethodTableLookup//方法中查找
__class_lookupMethodAndLoadCache3//跳轉->__class_lookupMethodAndLoadCache3 在runtime-class-new.mm 4856行
objc-runtime-new.mm
_class_lookupMethodAndLoadCache3
lookUpImpOrForward
getMethodNoSuper_nolock、search_method_list、log_and_fill_cache
cache_getImp、log_and_fill_cache、getMethodNoSuper_nolock、log_and_fill_cache
_class_resolveInstanceMethod
_objc_msgForward_impcache
objc-msg-arm64.s
STATIC_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
Core Foundation
__forwarding__(不開源)
複製代碼
objc_msgSend
是彙編寫的,在源碼objc-msg-arm64.s
304行,是objc_msgSend
的開始,_objc_msgSend
結束是351行, 進入到objc_msgSend
函數內部一探究竟:緩存
ENTRY _objc_msgSend // _objc_msgSend 開始
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 // 檢查p0寄存器是不是0 _objc_msgSend()第一個參數:self
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // if le < 0 -> 跳轉到標籤 LNilOrTagged
#else
b.eq LReturnZero // if le == 0 -> 跳轉到標籤 LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // 若是==0 -> LReturnZero
// tagged
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]
adrp x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
cmp x10, x16
b.ne LGetIsaDone
// 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
// SUPPORT_TAGGED_POINTERS
#endif
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret //return 返回結束掉
END_ENTRY _objc_msgSend // _objc_msgSend 結束
複製代碼
當objc_msgSend(id,SEL,arg)
的id
爲空的時候,跳轉標籤LNilOrTagged
,進入標籤內,當等於0則跳轉LReturnZero
,進入到LReturnZero
內,清除數據和return。不等於零,獲取isa和class,調用CacheLookup NORMAL
,進入到CacheLookup
內部bash
.macro CacheLookup //.macro 是一個宏 使用 _cmd&mask 查找緩存中的方法
// p1 = SEL, p16 = isa
ldp p10, p11, [x16, #CACHE] // p10 = buckets, p11 = occupied|mask
#if !__LP64__
and w11, w11, 0xffff // p11 = mask
#endif
and w12, w1, w11 // x12 = _cmd & mask
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp 命中 調用或者返回imp
2: // not hit: p12 = not-hit bucket 沒有命中
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // wrap: p12 = first bucket, w11 = mask
add p12, p12, w11, UXTW #(1+PTRSHIFT)
// p12 = buckets + (mask << 1+PTRSHIFT)
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // double wrap
JumpMiss $0
.endmacro
複製代碼
彙編代碼左邊是代碼,右邊是註釋,大概均可以看懂的。 當命中則return imp
,不然則跳轉CheckMiss
,進入到CheckMiss
內部:數據結構
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP
cbz p9, LGetImpMiss
.elseif $0 == NORMAL
cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
複製代碼
剛纔傳的值是NORMAL
,則跳轉__objc_msgSend_uncached
,進入到__objc_msgSend_uncached
內部(484行):架構
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
複製代碼
調用MethodTableLookup
,咱們查看MethodTableLookup
內部:app
.macro MethodTableLookup
// push frame
SignLR
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//跳轉->__class_lookupMethodAndLoadCache3 在runtime-class-new.mm 4856行
// 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
AuthenticateLR
.endmacro
複製代碼
最終跳轉到__class_lookupMethodAndLoadCache3
,去掉一個下劃線就是c函數,在runtime-class-new.mm 4856行
, 調用了函數lookUpImpOrForward(cls, sel, obj, YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
,第一次會初始化cls
和resolver
的值, 中最終跳轉到c/c++
函數lookUpImpOrForward
,該函數是最終能看到的c/c++
,如今咱們進入到lookUpImpOrForward
內部查看:iphone
/***********************************************************************
* lookUpImpOrForward.
* initialize==NO 儘可能避免調用,有時可能也會調用。
* cache==NO 跳過緩存查找,其餘地方可能會不調過
* 大多數人會傳值 initialize==YES and cache==YES
* 若是cls是非初始化的元類,則非Non-nil會快點
* May return _objc_msgForward_impcache. IMPs destined for external use
* must be converted to _objc_msgForward or _objc_msgForward_stret.
* 若是你不想用forwarding,則調用lookUpImpOrNil()代替
**********************************************************************/
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) { //從彙編過來是NO
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
runtimeLock.lock();
checkIsKnownClass(cls);
if (!cls->isRealized()) {
realizeClass(cls);
}
if (initialize && !cls->isInitialized()) {
//當cls須要初始化和沒有初始化的時候 進行cls初始化,
//初始化會加入到一個線程,同步執行,先初始化父類,再初始化子類
//數據的大小最小是4,擴容規則是:n*2+1;
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
}
retry:
runtimeLock.assertLocked();
//再次獲取imp
imp = cache_getImp(cls, sel);
if (imp) goto done;
//嘗試在本類中查找method
{//從cls->data()->methods查找method
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {//找到添加到cache中
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// Try superclass caches and method lists.
//從cls->superclass->data()->methods查找methd,supercls沒有查找出來,再查找父類的父類。
{
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.");
}
// Superclass cache.
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; } } } //若是尚未找到imp,進入動態方法解析階段 if (resolver && !triedResolver) { runtimeLock.unlock(); _class_resolveMethod(cls, sel, inst); runtimeLock.lock(); triedResolver = YES; goto retry; } //若是沒找到resolveInstanceMethod 和resolveClassMethod, // 進行消息轉發 階段 imp = (IMP)_objc_msgForward_impcache; //填充 cache cache_fill(cls, sel, imp, inst); done: runtimeLock.unlock(); return imp; } 複製代碼
SUPPORT_INDEXED_ISA
是在arm64
和LP64
還有arm_arch_7k>2
爲1,iphone
屬於arm64
、mac os
屬於LP64
,因此SUPPORT_INDEXED_ISA = 1
.
// Define SUPPORT_INDEXED_ISA=1 on platforms that store the class in the isa
// field as an index into a class table.
// Note, keep this in sync with any .s files which also define it.
// Be sure to edit objc-abi.h as well.
// __ARM_ARCH_7K__ 處理器架構指令集版本
//__arm64__ 架構
//__LP64__ uinx 和uinx mac os
#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
#endif
複製代碼
lookUpImpOrForward
函數的 大概思路以下:
首次已經從緩存中查過沒有命中因此再也不去緩存中查了,而後判斷cls
是否已經實現,cls->isRealized()
,沒有實現的話進行實現realizeClass(cls)
,主要是將初始化read-write data
和其餘的一些數據,後續會細講。而後進行cls
的初始化_class_initialize()
,當cls
須要初始化和沒有初始化的時候進行cls初始化,初始化會加入到一個線程,同步執行,先初始化父類,再初始化子類,數據的大小最小是4,擴容規則是:n*2+1
;而後再次獲取impcache_getImp
,而後在cls
方法中查找該method
,而後就是在superclass
中查找方法,直到父類是nil,找到的話,獲取imp
並將cls
和sel
加入到cache
中,不然進入到消息解析階段_class_resolveMethod
,在轉發階段,不是元類的話,進入到_class_resolveInstanceMethod
是元類的話調用_class_resolveClassMethod
,這兩種分別都會進入到lookUpImpOrNil
,再次查找IMP
,當沒找到的話就返回,找到的話用objc_msgSend
發送消息實現調用SEL_resolveInstanceMethod
並標記triedResolver
爲已動態解析標誌。而後進入到消息動態轉發階段_objc_msgForward_impcache
,至此runtime
發送消息結束。
借用網上找一個圖, 能夠更直觀的看出流程運轉。
realizeClass
是初始化了不少數據,包括cls->ro
賦值給cls->rw
,添加元類version
爲7,cls->chooseClassArrayIndex()
設置cls
的索引,supercls = realizeClass(remapClass(cls->superclass)); metacls = realizeClass(remapClass(cls->ISA()))
初始化superclass
和cls->isa
,後邊針對沒有優化的結構進行賦值這裏很少講,而後協調實例變量偏移佈局,設置cls->setInstanceSize
,拷貝flags
從ro
到rw
中,而後添加subclass
和rootclass
,最後添加類別的方法,協議,和屬性。
/***********************************************************************
* realizeClass
cls第一次初始化會執行,包括cls->rw->data(),返回真實的cls 結構體
runtimelock 必須有調用者把寫入鎖鎖起來
**********************************************************************/
static Class realizeClass(Class cls)
{
runtimeLock.assertLocked();
const class_ro_t *ro;
class_rw_t *rw;
Class supercls;
Class metacls;
bool isMeta;
if (!cls) return nil;
if (cls->isRealized()) return cls;
assert(cls == remapClass(cls));
// fixme verify class is not in an un-dlopened part of the shared cache?
//首先將tw賦值給to,由於數據結構同樣能夠直接強制轉化
ro = (const class_ro_t *)cls->data();
if (ro->flags & RO_FUTURE) {//是否已經初始化過,初始化過的哈 則 cls->rw 已經初始化過
rw = cls->data();
ro = cls->data()->ro;
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// 正常狀況下 申請class_rw_t空間
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro;//cls->rw->ro 指向如今的ro
rw->flags = RW_REALIZED|RW_REALIZING;//realized = 1 and realizing = 1
cls->setData(rw);//賦值
}
isMeta = ro->flags & RO_META;//是不是元類
rw->version = isMeta ? 7 : 0; // 元類版本是7,舊版的6,否就是0
// Choose an index for this class.
//設置cls的索引
cls->chooseClassArrayIndex();
if (PrintConnecting) {
_objc_inform("CLASS: realizing class '%s'%s %p %p #%u",
cls->nameForLogging(), isMeta ? " (meta)" : "",
(void*)cls, ro, cls->classArrayIndex());
}
// 若是父類沒有初始化則進行初始化
// root_class 作完須要設置RW_REALIZED=1,
// root metaclasses 須要執行完.
//從NXMapTable 獲取cls ,而後進行初始化
//從NXMapTable 獲取cls->isa ,而後進行初始化
supercls = realizeClass(remapClass(cls->superclass));
metacls = realizeClass(remapClass(cls->ISA()));
//沒有通過優化的isa執行的,如今已是version=7,在arm64上是優化過的,這個先不看了。
#if SUPPORT_NONPOINTER_ISA
// Disable non-pointer isa for some classes and/or platforms.
// Set instancesRequireRawIsa.
bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
bool rawIsaIsInherited = false;
static bool hackedDispatch = false;
if (DisableNonpointerIsa) {
// Non-pointer isa disabled by environment or app SDK version
instancesRequireRawIsa = true;
}
else if (!hackedDispatch && !(ro->flags & RO_META) &&
0 == strcmp(ro->name, "OS_object"))
{
// hack for libdispatch et al - isa also acts as vtable pointer
hackedDispatch = true;
instancesRequireRawIsa = true;
}
else if (supercls && supercls->superclass &&
supercls->instancesRequireRawIsa())
{
// This is also propagated by addSubclass()
// but nonpointer isa setup needs it earlier.
// Special case: instancesRequireRawIsa does not propagate
// from root class to root metaclass
instancesRequireRawIsa = true;
rawIsaIsInherited = true;
}
if (instancesRequireRawIsa) {
cls->setInstancesRequireRawIsa(rawIsaIsInherited);
}
// SUPPORT_NONPOINTER_ISA
#endif
// Update superclass and metaclass in case of remapping
cls->superclass = supercls;
cls->initClassIsa(metacls);
// 協調實例變量偏移/佈局
//可能從新申請空間 class_ro_t,更新咱們的class_ro_t
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
// 設置setInstanceSize 從ro->instanceSize
cls->setInstanceSize(ro->instanceSize);
//拷貝flags 從ro到rw中
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
cls->setHasCxxDtor();
if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
cls->setHasCxxCtor();
}
}
//添加superclass指針
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
// Attach categories
//類別的方法 在編譯的時候沒有添加到二進制文件中,在運行的時候添加進去的
methodizeClass(cls);
return cls;
}
複製代碼
這裏最後添加類別的數據是調用了methodizeClass
函數,這個函數首先添加method_list_t *list = ro->baseMethods()
到rw->methods.attachLists(&list, 1)
,而後將屬性property_list_t *proplist=ro->baseProperties
添加到rw->properties.attachLists(&proplist, 1)
,最後將協議列表protocol_list_t *protolist = ro->baseProtocols
追加到rw->protocols.attachLists(&protolist, 1)
,若是是metaclass
則添加SEL_initialize
,而後從全局NXMapTable *category_map
刪除已經加載的category_list
,最後調用attachCategories(cls, cats, false /*don't flush caches*/)
將已經加載的cats
的方法添加到cls->rw
上面而且不刷新caches
。
/***********************************************************************
* methodizeClass
修復cls方法列表想,協議列表和屬性列表
* 加鎖
**********************************************************************/
static void methodizeClass(Class cls)
{
runtimeLock.assertLocked();
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro;
// Methodizing for the first time
if (PrintConnecting) {
_objc_inform("CLASS: methodizing class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
//方法列表
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
//將對象的方法追加到cls->rw->methods後面
rw->methods.attachLists(&list, 1);
}
property_list_t *proplist = ro->baseProperties;
if (proplist) {
//將對象的屬性追加到rw->properties後面
rw->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
//將對象的協議追加到rw->protocols後面
rw->protocols.attachLists(&protolist, 1);
}
// Root classes get bonus method implementations if they don't have // them already. These apply before category replacements. if (cls->isRootMetaclass()) { // root metaclass addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO); } // Attach categories. //類別 從全局NXMapTable *category_map 已經加載過了。 category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/); //收集全部的cats到cls -> rw中 attachCategories(cls, cats, false /*don't flush caches*/);
if (PrintConnecting) {
if (cats) {
for (uint32_t i = 0; i < cats->count; i++) {
_objc_inform("CLASS: attached category %c%s(%s)",
isMeta ? '+' : '-',
cls->nameForLogging(), cats->list[i].cat->name);
}
}
}
if (cats) free(cats);//釋放cats
#if DEBUG
// Debug: sanity-check all SELs; log method list contents
for (const auto& meth : rw->methods) {
if (PrintConnecting) {
_objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-',
cls->nameForLogging(), sel_getName(meth.name));
}
assert(sel_registerName(sel_getName(meth.name)) == meth.name);
}
#endif
}
複製代碼
methodizeClass
以前rw
初始化的時候並無將其餘數據都都複製給rw
,如今methodizeClass
實現了將原本的ro
數據拷貝給rw
,而後attachCategories
將 分類的方法,屬性,協議追加到cls->data->rw
,咱們進入attachCategories
內部
static void attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// fixme rearrange to remove these intermediate allocations
//方法數組[[1,2,3],[4,5,6],[7,8,9]]
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
//屬性數組
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
//協議數組
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
// Count backwards through cats to get newest categories first
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;
bool fromBundle = NO;
while (i--) {
//取出某個分類
auto& entry = cats->list[i];
//取出分類 的 instance方法或者class方法
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist; //mlists 接受全部分類方法
fromBundle |= entry.hi->isBundle();
}
//proplist 接受全部分類屬性
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
//proplist 接受全部協議方法
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
//收集了全部協議 分類方法
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
//追加全部分類方法
rw->methods.attachLists(mlists, mcount);
//釋放數組
free(mlists);
//刷新該類的緩存
if (flush_caches && mcount > 0) flushCaches(cls);
//追加全部分類屬性
rw->properties.attachLists(proplists, propcount);
free(proplists);//釋放數組
//追加全部分類協議
rw->protocols.attachLists(protolists, protocount);
free(protolists);//釋放數組
}
複製代碼
添加attachLists
函數規則是後來的卻添加到內存的前部分,這裏就清楚爲何後編譯類別能後邊的類別覆蓋前邊的類別的相同名字的方法。
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
//一共須要的數量
uint32_t newCount = oldCount + addedCount;
//分配內存 內存不夠用了,須要擴容
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
//賦值count
array()->count = newCount;
// array()->lists:原來的方法列表向後移動 oldCount * sizeof(array()->lists[0]個長度
memmove(array()->lists + addedCount/*數組末尾*/, array()->lists/*數組*/,
oldCount * sizeof(array()->lists[0])/*移動的大小*/);
//空出來的 內存使用addedLists拷貝過去 大小是:addedCount * sizeof(array()->lists[0])
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
/*
圖示講解:
array()->lists:A->B->C->D->E
addedCount:3
addedLists:P->L->V
memmove以後:nil->nil->nil->A->B->C->D->E
而後再講addedLists插入到數組前邊,最終array()->lists的值是:
P->L->V->A->B->C->D->E
*/
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
複製代碼
class
初始化完成了,而後再次嘗試獲取imp = cache_getImp
,因爲緩存沒有中間也沒添加進去,因此這裏也是空的,而後從getMethodNoSuper_nolock
獲取該cls
的方法列表中查找,沒有的話再從superclass
查找cache
和method
,找到的話,進行log_and_fill_cache
至此消息發送完成。
動態解析函數_class_resolveMethod(cls, sel, inst)
,若是不是元類調用_class_resolveInstanceMethod
,若是是的話調用_class_resolveClassMethod
/***********************************************************************
* _class_resolveMethod
* 調用 +resolveClassMethod 或者 +resolveInstanceMethod
* 若是存在了則不檢查
**********************************************************************/
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {//不是元類則調用 實例的
//首先調用
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
//尋找classMethod
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
複製代碼
在resolveInstanceMethod
,查找SEL_resolveInstanceMethod
,傳值不用初始化,不用消息解析,可是cache
要查找。沒有找到的直接返回,找到的話使用objc_msgSend
發送消息調用SEL_resolveInstanceMethod
。
/***********************************************************************
* _class_resolveInstanceMethod
* 調用 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;
}
//若是找到SEL_resolveInstanceMethod 則使用objc_msgSend函數
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_resolveClassMethod
中,第一步先去lookUpImpOrNil
查找+SEL_resolveClassMethod
方法,沒找到的就結束,找到則調用objc_msgsend(id,sel)
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));
}
}
}
複製代碼
動態解析至此完成。
_objc_msgForward_impcache
是轉發的函數地址,在搜索框搜索發現,這個函數除了.s
文件中有,其餘地方均只是調用,說明這個函數是彙編實現,在objc-msg-arm64.s 531 行
發現一點蹤影
STATIC_ENTRY __objc_msgForward_impcache //開始__objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward//跳轉->__objc_msgForward
END_ENTRY __objc_msgForward_impcache // 結束__objc_msgForward_impcache
ENTRY __objc_msgForward // 開始 __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]//p17= x17 和 __objc_forward_handler@PAGEOFF的和
TailCallFunctionPointer x17 //跳轉-> TailCallFunctionPointer
END_ENTRY __objc_msgForward//結束 __objc_msgForward
複製代碼
當跳轉到adrp x17, __objc_forward_handler@PAGE
這一行,搜搜索函數_objc_forward_handler
,看到只是個打印函數,並無其餘函數來替代這個指針,那麼咱們用其餘方法來探究。
__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;
複製代碼
網上有大神總結的點咱們先參考下
// 僞代碼
int __forwarding__(void *frameStackPointer, int isStret) {
id receiver = *(id *)frameStackPointer;
SEL sel = *(SEL *)(frameStackPointer + 8);
const char *selName = sel_getName(sel);
Class receiverClass = object_getClass(receiver);
// 調用 forwardingTargetForSelector:
if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
id forwardingTarget = [receiver forwardingTargetForSelector:sel];
if (forwardingTarget && forwardingTarget != receiver) {
if (isStret == 1) {
int ret;
objc_msgSend_stret(&ret,forwardingTarget, sel, ...);
return ret;
}
return objc_msgSend(forwardingTarget, sel, ...);
}
}
// 殭屍對象
const char *className = class_getName(receiverClass);
const char *zombiePrefix = "_NSZombie_";
size_t prefixLen = strlen(zombiePrefix); // 0xa
if (strncmp(className, zombiePrefix, prefixLen) == 0) {
CFLog(kCFLogLevelError,
@"*** -[%s %s]: message sent to deallocated instance %p",
className + prefixLen,
selName,
receiver);
<breakpoint-interrupt>
}
// 調用 methodSignatureForSelector 獲取方法簽名後再調用 forwardInvocation
if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
if (methodSignature) {
BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct;
if (signatureIsStret != isStret) {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'. Signature thinks it does%s return a struct, and compiler thinks it does%s.",
selName,
signatureIsStret ? "" : not,
isStret ? "" : not);
}
if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];
[receiver forwardInvocation:invocation];
void *returnValue = NULL;
[invocation getReturnValue:&value];
return returnValue;
} else {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message",
receiver,
className);
return 0;
}
}
}
SEL *registeredSel = sel_getUid(selName);
// selector 是否已經在 Runtime 註冊過
if (sel != registeredSel) {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort",
sel,
selName,
registeredSel);
} // doesNotRecognizeSelector
else if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
[receiver doesNotRecognizeSelector:sel];
}
else {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort",
receiver,
className);
}
// The point of no return.
kill(getpid(), 9);
}
複製代碼
咱們簡單定義一個test
函數,而後並執行這個函數。
@interface Person : NSObject
- (void)test;
@end
@implementation Person
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%s",__func__);
if (sel == @selector(test)) {
Method me = class_getInstanceMethod(self, @selector(test2));
class_addMethod(self, sel,
method_getImplementation(me),
method_getTypeEncoding(me));
return YES;
}
return [super resolveInstanceMethod:sel];
}
-(void)test2{
NSLog(@"來了,老弟");
}
@end
Person *p = [[Person alloc]init];
[p test];
[p test];
//輸出
+[FYPerson resolveInstanceMethod:]
-[FYPerson test3]
-[FYPerson test3]
複製代碼
[p test]
在第一次執行的時候會走到消息動態解析的這一步,而後經過objc_msgsend
調用了test
,而且把test
添加到了緩存中,因此輸出了+[FYPerson resolveInstanceMethod:]
,在第二次調用的時候,會從緩存中查到imp
,因此直接輸出了-[FYPerson test3]
。
在+resolveInstanceMethod
能夠攔截掉實例方法的動態解析,在+resolveClassMethod
能夠攔截類方法。
@interface Person : NSObject
+ (void)test;
@end
+ (void)test3{
NSLog(@"來了,老弟");
}
+ (BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"%s",__func__);
if (sel == @selector(test)) {
Method me = class_getClassMethod(self, @selector(test3));//獲取method
//給sel 添加方法實現 @selecter(test3)
class_addMethod(object_getClass(self), sel,
method_getImplementation(me),
method_getTypeEncoding(me));
return YES;
}
return [super resolveInstanceMethod:sel];
}
[Person test];
//輸出
+[Person resolveClassMethod:]
來了,老弟
複製代碼
攔截+resolveClassMethod
,在條件爲sel==@selector(test)
的時候,將函數實現+test3()
的IMP
使用class_addMethod
添加到Person
上,待下次調用test
的時候直接經過imp = cache_getImp(cls, sel);
獲取到imp
函數指針而且執行。 咱們也能夠經過添加c函數的imp來實現給class添加函數實現。
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%s",__func__);
if (sel == @selector(test)) {
// Method me = class_getInstanceMethod(self, @selector(test3));
// class_addMethod(self.class, sel, method_getImplementation(me), method_getTypeEncoding(me));
class_addMethod(self.class, sel, (IMP)test3, "v16@0:8");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void test3(id self,SEL sel){
NSLog(@"test3:%s",NSStringFromSelector(sel).UTF8String);
}
//輸出
+[FYPerson resolveInstanceMethod:]
test3:test
test3:test
複製代碼
v16@0:8
是返回值爲void
參數佔用16字節大小,第一個是從0開始,第二個從8字節開始。 這段代碼和上面的其實本質上是同樣的,一個是給class
添加函數實現,使sel
和imp
對應起來,這個是將c
函數的imp
和sel
進行關聯,添加緩存以後,使用objc_msgsend()
效果是同樣的。
消息轉發可分爲3步,第一步根據- (id)forwardingTargetForSelector:(SEL)aSelector
返回的類對象或者元類對象,將方法轉發給該對象。假如第一步沒實現,則第二步根據返回的-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
或+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
函數簽名,在第三步(void)forwardInvocation:(NSInvocation *)anInvocation
調用函數[anInvocation invoke]
進行校驗成功以後進行調用函數。
@interface Person : NSObject
- (void)test;
@end
#import "Person.h"
#import "Student.h"
@implementation Person
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(test)) {
//objc_msgSend([[Struent alloc]init],test)
return [[Struent alloc]init];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
//輸出
-[Student test]
複製代碼
咱們定義了一個Person
只聲明瞭test
沒有實現,而後在消息轉發第一步forwardingTargetForSelector
將要處理的對象返回,成功調用了Student
的test
方法。
第一步沒攔截,能夠在第二步攔截。
//消息轉發第二步 沒有對象來處理方法,那將函數簽名來實現
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
NSMethodSignature *sign = [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
return sign;
}
return [super methodSignatureForSelector:aSelector];
}
// 函數簽名已返回,到了函數調用的地方
//selector 函數的sel
//target 函數調用者
//methodSignature 函數簽名
//NSInvocation 封裝數據的對象
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s",__func__);
}
//輸出
-[Person forwardInvocation:]
複製代碼
打印出了-[Person forwardInvocation:]
並且沒有崩潰,在forwardInvocation:(NSInvocation *)anInvocation
怎麼操做看開發者怎麼處理了,探究下均可以作什麼事情。 看到NSInvocation
的屬性和函數,sel
和target
是讀寫,函數簽名是必須的,因此(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
必須將函數簽名返回。
@property (readonly, retain) NSMethodSignature *methodSignature;//只讀
- (void)retainArguments;
@property (readonly) BOOL argumentsRetained;
@property (nullable, assign) id target;//讀寫
@property SEL selector;//讀寫
複製代碼
當攔截方法是類方法的時候,能夠用+ (id)forwardingTargetForSelector:(SEL)aSelecto
攔截,
//class 轉發
// 消息轉發第一步 攔截是否有轉發的class對象處理方法
+ (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(test3)) {
//objc_msgSend([[Struent alloc]init],test)
return [Student class];
}
return [super forwardingTargetForSelector:aSelector];
}
+ (void)test3{
// NSLog(@"+[Student test3]");
//當[Person test3]上一行寫這麼一行,Person *p = [[Person alloc]init] 這句報錯
//暫時不懂爲何不能調用NSLog,可是已經進來了因此調用了test2。
//註釋掉 [[Person alloc]init],一切正常。 有大佬瞭解嗎
}
- (void)test2{
NSLog(@"%s",__func__);
}
// 輸出
-[Student test2]
複製代碼
也能夠用返回return [[Student alloc]init];
將class
類方法轉化成實例方法,最後調用了Student
的對象方法test3
。其實本質上都是objc_msgSend(id,SEL,...)
,咱們修改的只是id
的值,id
類型在這段代碼中本質是對象,因此咱們能夠return instance
也能夠reurn class
。
+ (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(test3)) {
//objc_msgSend([[Struent alloc]init],test)
return [[Student alloc]init];
}
return [super forwardingTargetForSelector:aSelector];
}
- (void)test3{
NSLog(@"%s",__func__);
}
//輸出
-[Student test3]
複製代碼
將剛纔寫的methodSignatureForSelector
和forwardInvocation
改爲類方法,也是一樣能夠攔截類方法的。咱們看下
//消息轉發第二步 沒有class來處理方法,那將函數簽名來實現
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test3)) {
NSMethodSignature *sign = [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
return sign;
}
return [super methodSignatureForSelector:aSelector];
}
// 函數簽名已返回,到了函數調用的地方
//selector 函數的sel
//target 函數調用者
//methodSignature 函數簽名
//NSInvocation 封裝數據的對象
+ (void)forwardInvocation:(NSInvocation *)anInvocation{
// anInvocation.selector = @selector(test2);
//此處換成[Student class]一樣能夠
// anInvocation.target = (id)[[Student alloc]init];
// [anInvocation invoke];
NSLog(@"%s",__func__);
}
//輸出
+[Person forwardInvocation:]
複製代碼
測過其實對象方法和類方法都是用一樣的流程攔截的,對象方法是用-
方法,類方法是用+
方法。
cache->class_rw_t->supclass cache ->superclass class_rw_t ->動態解析
)+ (id)forwardingTargetForSelector:(SEL)aSelector
或- (id)forwardingTargetForSelector:(SEL)aSelector
攔截類或實例方法,能將對象方法轉發給其餘對象,也能將對象方法轉發給類方法,也能夠將類方法轉發給實例方法+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
或- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
實現攔截類和實例方法並返回函數簽名+ (void)forwardInvocation:(NSInvocation *)anInvocation
或- (void)forwardInvocation:(NSInvocation *)anInvocation
實現類方法和實例方法的調用和獲取返回值本文章之因此圖片比較少,我以爲仍是跟着代碼敲一遍,印象比較深入。
最怕一輩子碌碌無爲,還安慰本身平凡難得。
廣告時間