什么是Runtime
Objective-C是一門動態性比較強的編程語言,跟C、C++等語言有著很大的不同;Objective-C的動態性是由Runtime API來支撐的
Runtime API提供的介面基本都是C語言的,原始碼由C\C++\匯編語言撰寫
方法型別的底層結構
在Class物件的底層結構objc_class中,我們知道通過bits & FAST_DATA_MASK就可以得到class_rw_t型別的表結構
class_rw_t里面的methods、properties、protocols都是二維陣列,是可讀可寫的,包含了類的初始內容、分類的內容

以method_array_t舉例,里面的元素都是method_list_t型別的二維陣列,每一個二維陣列又是method_t型別的元素,表示每一個方法型別
class method_array_t :
public list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>
{
typedef list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> Super;
public:
method_array_t() : Super() { }
method_array_t(method_list_t *l) : Super(l) { }
....
};
class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一維陣列,是只讀的,包含了類的初始內容

在objc原始碼里的頭檔案objc-runtime-new.mm,通過查看函式realizeClassWithoutSwift的實作,程式運行時會將class_ro_t里面的資料和分類里面的資料資訊全部合并到一起放到class_rw_t里面來
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
....
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro();
ASSERT(!isMeta);
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// 分配記憶體空間,將ro的資料放到rw
rw = objc::zalloc<class_rw_t>();
rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
}
cls->cache.initializeToEmptyOrPreoptimizedInDisguise();
....
}
method_t
method_t是對方法\函式的封裝,里面包含了函式名,編碼資訊以及函式地址
struct method_t {
static const uint32_t smallMethodListFlag = 0x80000000;
method_t(const method_t &other) = delete;
struct big {
SEL name; // 函式名
const char *types; // 編碼(回傳值型別、引數型別)
MethodListIMP imp; // 指向函式的指標
};
}
IMP代表函式的具體實作,指向著該函式的地址
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
SEL代表方法\函式名,一般叫做選擇器,底層結構跟char *類似,可以通過@selector()和sel_registerName()獲得
typedef struct objc_selector *SEL;
不同類中相同名字的方法,所對應的方法選擇器是相同的,可以通過sel_getName()和NSStringFromSelector()轉成字串
types包含了函式回傳值、引數編碼的字串,排列順序如下

iOS中提供了一個叫做@encode的指令,可以將具體的型別表示成字串編碼

一個函式默認會帶有兩個引數id _Nonnull和SEL _Nonnull,之后才是寫入的引數
下面舉例說明,函式的types是多少
- (int)test:(int)age height:(float)height
{
NSLog(@"%s", __func__);
return 0;
}
// 該函式types為i24@0:8i16f20
// i 回傳值int型別
// 24 幾個回傳值型別占據的大小總和(8 + 8 + 4 + 4)
// @ id型別
// 0 表示從第0位開始
// : SEL型別
// 8 從第8位開始
// i 引數int型別
// 16 從第16位開始
// f 引數float型別
// 20 從第20位開始
方法快取
Class內部結構中有個方法快取cache_t,用散串列(哈希表)來快取曾經呼叫過的方法,可以提高方法的查找速度
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
union {
struct {
explicit_atomic<mask_t> _maybeMask; // 散串列的長度 - 1
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied; // 已經快取的方法數量
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
....
mask_t mask() const;
public:
unsigned capacity() const;
struct bucket_t *buckets() const; // 散串列
Class cls() const;
....
}
散串列bucket_t內部有SEL作為key和函式地址IMP一一對應
struct bucket_t {
private:
// IMP-first is better for arm64e ptrauth and no worse for arm64.
// SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
explicit_atomic<uintptr_t> _imp; // 函式的記憶體地址
explicit_atomic<SEL> _sel; // sel為key
#else
explicit_atomic<SEL> _sel;
explicit_atomic<uintptr_t> _imp;
#endif
....
}
在objc-cache.mm檔案里可以查看 cache_t::insert函式,是通過一套哈希演算法計算出索引,然后根據索引在散串列陣列里直接插入資料進行快取
void cache_t::insert(SEL sel, IMP imp, id receiver) {
....
mask_t newOccupied = occupied() + 1;
unsigned oldCapacity = capacity(), capacity = oldCapacity;
if (slowpath(isConstantEmptyCache())) {
// Cache is read-only. Replace it.
if (!capacity) capacity = INIT_CACHE_SIZE;
reallocate(oldCapacity, capacity, /* freeOld */false);
}
else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
// Cache is less than 3/4 or 7/8 full. Use it as-is.
}
#if CACHE_ALLOW_FULL_UTILIZATION
else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) {
// Allow 100% cache utilization for small buckets. Use it as-is.
}
#endif
else {
// 如果空間已滿,那么就進行擴容,乘以2倍
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
// 將舊的快取釋放,清空快取,然后設定最新的mask值
reallocate(oldCapacity, capacity, true);
}
bucket_t *b = buckets();
mask_t m = capacity - 1;
// 通過 sel&mask 計算出索引(哈希演算法)
mask_t begin = cache_hash(sel, m);
mask_t i = begin;
do {
// 通過索引找到的該SEL為空,那么就插入bucket_t
if (fastpath(b[i].sel() == 0)) {
incrementOccupied();
b[i].set<Atomic, Encoded>(b, sel, imp, cls());
return;
}
// 用索引從bucket里面取sel和傳進來的sel做比較,如果一樣證明已經存有,直接回傳
if (b[i].sel() == sel) {
return;
}
// 從散串列里查找,如果上述條件不成立(索引沖突),那么通過cache_next計算出新的索引再查找插入
} while (fastpath((i = cache_next(i, m)) != begin));
bad_cache(receiver, (SEL)sel);
#endif // !DEBUG_TASK_THREADS
}
下面是cache_t::insert的一些詳細呼叫決議
當存盤空間已滿時,會進行擴容,并且將舊的快取全部釋放清空,然后設定最新的mask值,mask值是散串列的存盤容量-1,也正好對應散串列的索引值
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld) {
bucket_t *oldBuckets = buckets();
bucket_t *newBuckets = allocateBuckets(newCapacity);
ASSERT(newCapacity > 0);
ASSERT((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);
setBucketsAndMask(newBuckets, newCapacity - 1);
// 將舊的快取和mask釋放
if (freeOld) {
collect_free(oldBuckets, oldCapacity);
}
}
cache_hash的哈希演算法就是將mask進行一次位運算,所得的索引值只會小于等于mask值
static inline mask_t cache_hash(SEL sel, mask_t mask) {
uintptr_t value = https://www.cnblogs.com/funkyRay/p/(uintptr_t)sel;
#if CONFIG_USE_PREOPT_CACHES
value ^= value >> 7;
#endif
return (mask_t)(value & mask);
}
如果計算出的索引在散串列中已經有了快取資料,那么就通過cache_next更新下索引值,再去對應的位置插入快取資料
通過原始碼可以看到計算方式如下:
有沖突的索引如果不為0就直接索引值減1,然后再根據新的索引值去散串列中對應插入
如果沖突的索引為0,那么直接就將mask賦值給新的索引值,再去對應查找插入
#if CACHE_END_MARKER
static inline mask_t cache_next(mask_t i, mask_t mask) {
return (i+1) & mask;
}
#elif __arm64__
// arm64架構下如果索引非0,就是i-1,索引為0回傳mask
static inline mask_t cache_next(mask_t i, mask_t mask) {
return i ? i-1 : mask;
}
快取的資料在散串列中都對應著一定的空間,所以這套查找演算法就是利用了空間換時間,來增加效率

方法呼叫的底層結構
我們先將下面的代碼通過Clang的命令生成C++代碼,如下所示
@interface Person : NSObject
- (void)test;
+ (void)test;
@end
@implementation Person
- (void)test
{
NSLog(@"%s", __func__);
}
+ (void)test
{
NSLog(@"%s", __func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person test];
[Person test];
}
return 0;
}
// 轉換成C++檔案后的兩個呼叫方法為:
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("test"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("test"));
發現函式呼叫最后都是轉化為objc_msgSend,尤其我們可以推斷出方法的呼叫本質就是objc_msgSend訊息發送
由于sel_registerName和@selector回傳值都是SEL,我們通過列印兩個方法的地址是一樣的,可以確定兩個方法是可以等同的
NSLog(@"%p, %p", @selector(test), sel_registerName("test"));
// 列印結果都是0x100003f66
我們對兩個C++函式簡化之后,就得到以下兩個方法
objc_msgSend(person, @selector(test));
// 訊息接收者(receiver):person
// 訊息名稱:test
objc_msgSend([Person class], @selector(test));
// 訊息接收者(receiver):[Person class]
// 訊息名稱:test
而且第一個引數分別可以寫為person和[Person class],也可以稱為訊息的接收者
第二個引數都是test,SEL就是訊息的名稱
方法呼叫的執行流程
objc_msgSend的執行流程可以分為3大階段
- 訊息發送
- 動態方法決議
- 訊息轉發
訊息發送
我們在objc原始碼里全域搜索關鍵字objc_msgSend可以發現,訊息發送的入口一開始是在objc-msg-arm64.s中通過匯編來實作第一步的
下面我們開始分析原始碼
【第一步】 這里主要就是判斷receiver是否有值,然后對應的跳轉,isa指標和ISA_MASK進行位運算獲取到Class資料,并且要先去查找是否有快取方法,如果沒有則要去更深的Class資料中查找了
// MARK: _objc_msgSend的實作:匯編入口(ENTRY是入口的意思)
ENTRY _objc_msgSend
// 無視窗
UNWIND _objc_msgSend, NoFrame
// p0暫存器里存盤的值(也就是引數receiver)和0做對比(cmp是比較的意思)
cmp p0, #0 // nil check and tagged pointer check
// 支持TAGGED_POINTERS的流程
#if SUPPORT_TAGGED_POINTERS
// 如果小于等于0,則跳轉LNilOrTagged
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
// 如果等于0,則跳轉LReturnZero
b.eq LReturnZero
#endif
// 根據物件拿出isa ,即從x0暫存器指向的地址 取出 isa,存入 p13暫存器
ldr p13, [x0] // p13 = isa
// 在64位架構下通過 p16 = isa(p13) & ISA_MASK,拿出shiftcls資訊,得到class資訊
GetClassFromIsa_p16 p13, 1, x0 // p16 = class
LGetIsaDone:
// calls imp or objc_msgSend_uncached
// 如果從快取中找不到,則跳轉__objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
// 如果等于0,則跳轉LReturnZero
b.eq LReturnZero // nil check
GetTaggedClass
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
END_ENTRY _objc_msgSend
ENTRY _objc_msgLookup
UNWIND _objc_msgLookup, NoFrame
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LLookup_NilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LLookup_Nil
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13, 1, x0 // p16 = class
LLookup_GetIsaDone:
// returns imp
CacheLookup LOOKUP, _objc_msgLookup, __objc_msgLookup_uncached
#if SUPPORT_TAGGED_POINTERS
LLookup_NilOrTagged:
b.eq LLookup_Nil // nil check
GetTaggedClass
b LLookup_GetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
LLookup_Nil:
adr x17, __objc_msgNil
SignAsImp x17
ret
END_ENTRY _objc_msgLookup
STATIC_ENTRY __objc_msgNil
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
END_ENTRY __objc_msgNil
ENTRY _objc_msgSendSuper
UNWIND _objc_msgSendSuper, NoFrame
ldp p0, p16, [x0] // p0 = real receiver, p16 = class
b L_objc_msgSendSuper2_body
END_ENTRY _objc_msgSendSuper
下面是_objc_msgSend的一些詳細呼叫決議
GetClassFromIsa_p16里是isa指標進行&ISA_MASK的運算程序
// MARK: GetClassFromIsa_p16
// 宏
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */
#if SUPPORT_INDEXED_ISA
// 將isa的值存入p16暫存器
mov p16, \src // optimistically set dst = src
tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa
// isa in p16 is indexed
// 將_objc_indexed_classes所在的頁的基址 讀入x10暫存器
adrp x10, _objc_indexed_classes@PAGE
// x10 = x10 + _objc_indexed_classes(page中的偏移量)(x10基址 根據 偏移量 進行 記憶體偏移)
add x10, x10, _objc_indexed_classes@PAGEOFF
// 從p16的第ISA_INDEX_SHIFT位開始,提取 ISA_INDEX_BITS 位 到 p16暫存器,剩余的高位用0補充
ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract index
ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
1:
#elif __LP64__
.if \needs_auth == 0 // _cache_getImp takes an authed class already
mov p16, \src
.else
// 64-bit packed isa
ExtractISA p16, \src, \auth_address
.endif
#else
// 32-bit raw isa
mov p16, \src
#endif
.endmacro
在CacheLookup里進行快取查找的程序,主要就是SEL & MASK得出一個索引,根據索引去buckets散串列中取對應的方法資料
// MARK: CacheLookup
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
//
// Restart protocol:
//
// As soon as we're past the LLookupStart\Function label we may have
// loaded an invalid cache pointer or mask.
//
// When task_restartable_ranges_synchronize() is called,
// (or when a signal hits us) before we're past LLookupEnd\Function,
// then our PC will be reset to LLookupRecover\Function which forcefully
// jumps to the cache-miss codepath which have the following
// requirements:
//
// GETIMP:
// The cache-miss is just returning NULL (setting x0 to 0)
//
// NORMAL and LOOKUP:
// - x0 contains the receiver
// - x1 contains the selector
// - x16 contains the isa
// - other registers are set as per calling conventions
//
mov x15, x16 // stash the original isa
LLookupStart\Function:
// p1 = SEL, p16 = isa
// mac os或者模擬器
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
ldr p10, [x16, #CACHE] // p10 = mask|buckets
lsr p11, p10, #48 // p11 = mask
and p10, p10, #0xffffffffffff // p10 = buckets
and w12, w1, w11 // x12 = _cmd & mask
// 64位真機
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
// 從x16(即isa)中取出cache 存入p11暫存器
ldr p11, [x16, #CACHE] // p11 = mask|buckets
#if CONFIG_USE_PREOPT_CACHES
// 下面幾個是通過位運算& mask得出索引存入p12
#if __has_feature(ptrauth_calls)
tbnz p11, #0, LLookupPreopt\Function
and p10, p11, #0x0000ffffffffffff // p10 = buckets
#else
and p10, p11, #0x0000fffffffffffe // p10 = buckets
tbnz p11, #0, LLookupPreopt\Function
#endif
eor p12, p1, p1, LSR #7
and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
and p10, p11, #0x0000ffffffffffff // p10 = buckets
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
#endif // CONFIG_USE_PREOPT_CACHES
// 非64位真機
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
ldr p11, [x16, #CACHE] // p11 = mask|buckets
and p10, p11, #~0xf // p10 = buckets
and p11, p11, #0xf // p11 = maskShift
mov p12, #0xffff
lsr p11, p12, p11 // p11 = mask = 0xffff >> p11
and p12, p1, p11 // x12 = _cmd & mask
#else
#error Unsupported cache mask storage for ARM64.
#endif
// #define PTRSHIFT 3
// LSL #(1+PTRSHIFT)-- 實際含義就是得到一個bucket占用的記憶體大小
add p13, p10, p12, LSL #(1+PTRSHIFT)
// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
// do {
// 從x13(即p13)中取出 bucket 分別將imp和sel 存入 p17(存盤imp) 和 p9(存盤sel)
1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
// 比較 sel 與 p1(傳入的引數cmd)
cmp p9, p1 // if (sel != _cmd) {
// ne == not equal,請跳轉至 3f
b.ne 3f // scan more
// } else {
// 如果相等 即CacheHit 快取命中,直接回傳imp
2: CacheHit \Mode // hit: call or return imp
// }
3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss;
cmp p13, p10 // } while (bucket >= buckets)
b.hs 1b
// wrap-around:
// p10 = first bucket
// p11 = mask (and maybe other bits on LP64)
// p12 = _cmd & mask
//
// A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.
// So stop when we circle back to the first probed bucket
// rather than when hitting the first bucket again.
//
// Note that we might probe the initial bucket twice
// when the first probed slot is the last entry.
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
add p13, p10, w11, UXTW #(1+PTRSHIFT)
// p13 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
// p13 = buckets + (mask << 1+PTRSHIFT)
// see comment about maskZeroBits
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
add p13, p10, p11, LSL #(1+PTRSHIFT)
// p13 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = first probed bucket
// do {
4: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
cmp p9, p1 // if (sel == _cmd)
b.eq 2b // goto hit
cmp p9, #0 // } while (sel != 0 &&
ccmp p13, p12, #0, ne // bucket > first_probed)
b.hi 4b
LLookupEnd\Function:
LLookupRecover\Function:
b \MissLabelDynamic
#if CONFIG_USE_PREOPT_CACHES
#if CACHE_MASK_STORAGE != CACHE_MASK_STORAGE_HIGH_16
#error config unsupported
#endif
LLookupPreopt\Function:
#if __has_feature(ptrauth_calls)
and p10, p11, #0x007ffffffffffffe // p10 = buckets
autdb x10, x16 // auth as early as possible
#endif
// x12 = (_cmd - first_shared_cache_sel)
adrp x9, _MagicSelRef@PAGE
ldr p9, [x9, _MagicSelRef@PAGEOFF]
sub p12, p1, p9
// w9 = ((_cmd - first_shared_cache_sel) >> hash_shift & hash_mask)
#if __has_feature(ptrauth_calls)
// bits 63..60 of x11 are the number of bits in hash_mask
// bits 59..55 of x11 is hash_shift
lsr x17, x11, #55 // w17 = (hash_shift, ...)
lsr w9, w12, w17 // >>= shift
lsr x17, x11, #60 // w17 = mask_bits
mov x11, #0x7fff
lsr x11, x11, x17 // p11 = mask (0x7fff >> mask_bits)
and x9, x9, x11 // &= mask
#else
// bits 63..53 of x11 is hash_mask
// bits 52..48 of x11 is hash_shift
lsr x17, x11, #48 // w17 = (hash_shift, hash_mask)
lsr w9, w12, w17 // >>= shift
and x9, x9, x11, LSR #53 // &= mask
#endif
ldr x17, [x10, x9, LSL #3] // x17 == sel_offs | (imp_offs << 32)
cmp x12, w17, uxtw
.if \Mode == GETIMP
b.ne \MissLabelConstant // cache miss
sub x0, x16, x17, LSR #32 // imp = isa - imp_offs
SignAsImp x0
ret
.else
b.ne 5f // cache miss
sub x17, x16, x17, LSR #32 // imp = isa - imp_offs
.if \Mode == NORMAL
br x17
.elseif \Mode == LOOKUP
orr x16, x16, #3 // for instrumentation, note that we hit a constant cache
SignAsImp x17
ret
.else
.abort unhandled mode \Mode
.endif
5: ldursw x9, [x10, #-8] // offset -8 is the fallback offset
add x16, x16, x9 // compute the fallback isa
b LLookupStart\Function // lookup again with a new isa
.endif
#endif // CONFIG_USE_PREOPT_CACHES
.endmacro
【第二步】 上述一系列操作如果沒有取到方法快取,那么就會進到__objc_msgSend_uncached中
// MARK: __objc_msgSend_uncached
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p15 is the class to search
// 跳轉到MethodTableLookup
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
STATIC_ENTRY __objc_msgLookup_uncached
UNWIND __objc_msgLookup_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p15 is the class to search
MethodTableLookup
ret
END_ENTRY __objc_msgLookup_uncached
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0, 0
CacheLookup GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstant
LGetImpMissDynamic:
mov p0, #0
ret
LGetImpMissConstant:
mov p0, p2
ret
END_ENTRY _cache_getImp
再進一步跳轉到MethodTableLookup,發現最侄訓呼叫到C語言函式lookUpImpOrForward中
// MARK: MethodTableLookup
.macro MethodTableLookup
SAVE_REGS MSGSEND
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
mov x2, x16
mov x3, #3
// 跳轉到C語言函式 lookUpImpOrForward
bl _lookUpImpOrForward
// IMP in x0
mov x17, x0
RESTORE_REGS MSGSEND
.endmacro
// MARK: __objc_msgSend_uncached
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p15 is the class to search
// 跳轉到MethodTableLookup
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
知識點: C的函式名稱對應到匯編中都會在其函式名之前再加上一個_,作為函式名稱
【第三步】 跳轉到objc-rumtime-new.mm的lookUpImpOrForward函式來看,會到當前類的方法串列里查找,如果沒有再去父類的方法快取以及方法串列中查找,直到找到呼叫為止;如果都沒有找到,那么就會進入到方法決議的階段
NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
// 定義的訊息轉發
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
runtimeLock.assertUnlocked();
// 判斷類是否初始化,如果沒有,需要先初始化
if (slowpath(!cls->isInitialized())) {
behavior |= LOOKUP_NOCACHE;
}
// 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.lock();
// We don't want people to be able to craft a binary blob that looks like
// a class but really isn't one and do a CFI attack.
//
// To make these harder we want to make sure this is a class that was
// either built into the binary or legitimately registered through
// objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.
checkIsKnownClass(cls);
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
// runtimeLock may have been dropped but is now locked again
runtimeLock.assertLocked();
curClass = cls;
// The code used to lookup the class's cache again right after
// we take the lock but for the vast majority of the cases
// evidence shows this is a miss most of the time, hence a time loss.
//
// The only codepath calling into this without having performed some
// kind of cache lookup is class_getInstanceMethod().
// 查找類的快取
// unreasonableClassCount -- 表示類的迭代的上限
for (unsigned attempts = unreasonableClassCount();;) {
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
// 又會在快取里找一次,如果找的就回傳
imp = cache_getImp(curClass, sel);
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
// 當前類方法串列(采用二分法查找)
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) { // 如果存在,取出imp,存到快取中
imp = meth->imp(false);
goto done;
}
// 當前類 = 當前類的父類,并判斷父類是否為nil
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// 未找到方法實作,方法決議器也不行,使用轉發
imp = forward_imp;
break;
}
}
// 如果父類鏈中存在回圈,則停止
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// 父類快取
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
// 如果在父類中找到了forward,則停止查找,且不快取,首先呼叫此類的方法決議器
break;
}
if (fastpath(imp)) {
// 如果在父類中,找到了此方法,將其存盤到cache中
goto done;
}
}
// 沒有找到方法實作,嘗試一次方法決議
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
cls = cls->cache.preoptFallbackClass();
}
#endif
// 將方法填充到快取中
log_and_fill_cache(cls, imp, sel, inst, curClass);
}
done_unlock:
// 解鎖
runtimeLock.unlock();
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
下面是lookUpImpOrForward的一些詳細呼叫決議
1.上述函式會根據傳進來的類遍歷查找,而且每次都要先去_cache_getImp中查找是否有方法快取,_cache_getImp里又會呼叫回CacheLookup進一步查找
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0, 0
CacheLookup GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstant
LGetImpMissDynamic:
mov p0, #0
ret
LGetImpMissConstant:
mov p0, p2
ret
END_ENTRY _cache_getImp
2.在getMethodNoSuper_nolock里會找到class_rw_t的methods方法串列里進行遍歷查找
static method_t
*getMethodNoSuper_nolock(Class cls, SEL sel) {
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
// 從類物件里拿到class_rw_t的methods
auto const methods = cls->data()->methods();
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
// <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest
// caller of search_method_list, inlining it turns
// getMethodNoSuper_nolock into a frame-less function and eliminates
// any store from this codepath.
method_t *m = search_method_list_inline(*mlists, sel);
if (m) return m;
}
return nil;
}
在search_method_list_inline里會根據排序來選擇是采用二分查找還是線性查找
ALWAYS_INLINE static
method_t *search_method_list_inline(const method_list_t *mlist, SEL sel) {
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->isExpectedSize();
// 如果排好序的就用二分查找
if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
return findMethodInSortedMethodList(sel, mlist);
} else { // 線性查找,就是一個個找
// Linear search of unsorted method list
if (auto *m = findMethodInUnsortedMethodList(sel, mlist))
return m;
}
#if DEBUG
// sanity-check negative results
if (mlist->isFixedUp()) {
for (auto& meth : *mlist) {
if (meth.name() == sel) {
_objc_fatal("linear search worked when binary search did not");
}
}
}
#endif
return nil;
}
在findMethodInSortedMethodList中進行二分查找
template<class getNameFunc>
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName) {
ASSERT(list);
auto first = list->begin();
auto base = first;
decltype(first) probe;
uintptr_t keyValue = https://www.cnblogs.com/funkyRay/p/(uintptr_t)key;
uint32_t count;
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)getName(probe);
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
probe--;
}
return &*probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
在findMethodInUnsortedMethodList中進行線性查找,也就是一個個往下遍歷查找
template<class getNameFunc>
ALWAYS_INLINE static method_t *
findMethodInUnsortedMethodList(SEL sel, const method_list_t *list, const getNameFunc &getName)
{
for (auto& meth : *list) {
if (getName(meth) == sel) return &meth;
}
return nil;
}
ALWAYS_INLINE static method_t *
findMethodInUnsortedMethodList(SEL key, const method_list_t *list)
{
if (list->isSmallList()) {
if (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS && objc::inSharedCache((uintptr_t)list)) {
return findMethodInUnsortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSEL(); });
} else {
return findMethodInUnsortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSELRef(); });
}
} else {
return findMethodInUnsortedMethodList(key, list, [](method_t &m) { return m.big().name; });
}
}
3.在log_and_fill_cache里將查找到的方法插入到快取中,最后呼叫到objc-cache.mm中的cache_t::insert函式,該函式的詳細決議可以查看文章一開始的cache_t部分內容
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer) {
#if SUPPORT_MESSAGE_LOGGING
if (slowpath(objcMsgLogEnabled && implementer)) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
cls->cache.insert(sel, imp, receiver);
}
總結
整個訊息發送的流程可以用下圖來概述

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/273578.html
標籤:iOS
下一篇:iOS-宮格拼圖
