上篇我們講過了iOS底層原理 runtime-object_class拾遺基礎,arm64之後isa是使用聯合體使用更少的空間儲存更多的資料,以及如何自定義和使用聯合體,objc_class->cache_t cache
是一個是快取最近呼叫class
的方法,當快取剩餘空間小余1/4則進行擴容,擴容為原來的兩倍,擴容之後,已儲存的method_t
擴容之後之後被清空。今天我們在瞭解runtime的訊息轉發機制。
基礎知識
OC中的方法呼叫,其實都是轉換為objc_msgSend函式的呼叫
objc_msgSend的執行流程可以分為3大階段
- 訊息傳送
- 動態方法解析
- 訊息轉發
那麼我們根據這三塊內容進行原始碼解讀。原始碼執行的順序大概如下
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
內部
.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
內部:
.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
內部檢視:
/***********************************************************************
* 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()解析
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
}
複製程式碼
attachCategories()解析
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);//釋放陣列
}
複製程式碼
rw->list->attachLists()解析
新增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:]
複製程式碼
測過其實物件方法和類方法都是用同樣的流程攔截的,物件方法是用-
方法,類方法是用+
方法。
總結
- objc_msgSend傳送訊息,會首先在cache中查詢,查詢不到則去方法列表(順序是
cache->class_rw_t->supclass cache ->superclass class_rw_t ->動態解析
) - 第二步是動態解析,能在resolveInstanceMethod或+ (BOOL)resolveClassMethod:(SEL)sel來來攔截,可以給class新增實現函式,達到不崩潰目的
- 第三步是訊息轉發,轉發第一步可以在
+ (id)forwardingTargetForSelector:(SEL)aSelector
或- (id)forwardingTargetForSelector:(SEL)aSelector
攔截類或例項方法,能將物件方法轉發給其他物件,也能將物件方法轉發給類方法,也可以將類方法轉發給例項方法 - 第三步訊息轉發的第二步可以在
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
或- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
實現攔截類和例項方法並返回函式簽名 - 第三步訊息轉發的第三步可以
+ (void)forwardInvocation:(NSInvocation *)anInvocation
或- (void)forwardInvocation:(NSInvocation *)anInvocation
實現類方法和例項方法的呼叫和獲取返回值
資料下載
本文章之所以圖片比較少,我覺得還是跟著程式碼敲一遍,印象比較深刻。
最怕一生碌碌無為,還安慰自己平凡可貴。
廣告時間