runtime翻譯過來稱爲運行時,與之對應的是編譯時。大部分的iOS開發人員,都聽過runtime這個詞,也知道運行時,但只是停留在表面,只是知道而已,並無去深刻的去探索和分析過。html
OC語言是一門動態語言,擁有動態語言的三大特性:動態類型、動態綁定、動態加載。而底層實現就是熟悉又陌生的Runtime。編程
runtime的使用的三種方式,其三種實現方法與編譯層和底層的關係如圖所示緩存
NSObject
方法實現,例如isKindOfClass
Runtime API
底層方法實現,例如class_getImstanceSize
圖中的compiler就是編譯器,就是咱們熟悉的LLVMmarkdown
咱們知道平時寫的OC代碼,底層實現其實都是C/C++的代碼實現的,再通過編譯器LLVM編譯,最終轉化爲機器語言。 經過clang編譯的源碼,理解了OC對象的本質是結構體,一樣的,咱們也可使用clang命令編譯成main.cpp文件,看安方法的本質是什麼?架構
在main函數中,寫入如下代碼app
LGPerson *person = [LGPerson alloc];
[person sayNB];
[person say:@"NB"];
複製代碼
生成cpp文件,底層代碼以下編程語言
LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("say:"), (NSString *)&__NSConstantStringImpl__var_folders_jl_d06jlfkj2ws74_5g45kms07m0000gn_T_main_d8842a_mi_3);
複製代碼
能夠看出,方法的本質:objc_msgSend
消息發送 objc_msgSend
的參數函數
SEL
+參數)在Build Setting中,將Enable Strict Checking of obc_msgSend Calls
設置爲NO,將嚴厲的檢查機制關掉,不然objc_msgSend的參數會報錯 導入頭文件post
#import <objc/message.h>
複製代碼
在main函數中,寫入如下代碼ui
@interface LGPerson : NSObject
- (void)sayNB;
@end
@implementation LGPerson
- (void)sayNB{ NSLog(@"666"); }
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));
}
return 0;
}
-------------------------
//輸出結果:
666
複製代碼
子類調用父類方法時,可以使用objc_msgSendSuper,向父類發送消息 在objc源碼中,找到objc_msgSendSuper的定義
OBJC_EXPORT void
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
複製代碼
找到objc_super結構體的定義
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
__unsafe_unretained _Nonnull Class super_class;
/* super_class is the first class to search */
};
複製代碼
在項目中調用objc_msgSendSuper 定義LGeacher,繼承於LGPerson
@interface LGTeacher : LGPerson
- (void)sayNB;
@end
@implementation LGTeacher
@end
複製代碼
在main函數中,寫入如下代碼
struct lg_objc_super {
__unsafe_unretained _Nonnull id receiver;
__unsafe_unretained _Nonnull Class super_class;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGTeacher *teacher = [LGTeacher alloc];
struct lg_objc_super lgSuper;
lgSuper.receiver=teacher;
lgSuper.super_class=[LGPerson class];
objc_msgSendSuper(&lgSuper, @selector(sayNB));
}
return 0;
}
-------------------------
//輸出結果:
666
複製代碼
objc_super
相同結構,定義lg_objc_super
結構體LGTeacher
中沒有sayNB
方法的實現,因此super_class
直接傳入LGPerson
的類對象super_class
傳入LGTeacher
的類方法,打印結果相同。區別在於須要向父類多查找一層。在objc4-818.2源碼中,不一樣系統架構的彙編指令都有差別,咱們只針對最經常使用的arn64架構下的彙編代碼進行探索
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13, 1, x0 // p16 = class
複製代碼
ENTRY _objc_msgSend
:入口cmp p0, #0
:消息接收者和#0比較
SUPPORT_TAGGED_POINTERS
:真機arm64架構,SUPPORT_TAGGED_POINTERS
定義爲1b.le LNilOrTagger
:小於等於0,進入LNilOrTagged
流程b.eq LReturnZero
:等於0,進入LReturnZero流程ldr p13, [x0]
:將X0寄存器取地址,賦值p13寄存器p13
:存儲消息接收者的isaGetClassFromIsa_p16
流程,傳入isa、一、消息接收者LNilOrTagged:
b.eq LReturnZero // nil check
GetTaggedClass
b LGetIsaDone
複製代碼
LReturnZero
流程GetTaggedClass
流程LGetIsaDone
流程LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
複製代碼
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */
#if SUPPORT_INDEXED_ISA
// Indexed isa
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
adrp x10, _objc_indexed_classes@PAGE
add x10, x10, _objc_indexed_classes@PAGEOFF
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
複製代碼
src
:isa
needs_auth
:1auth_address
:消息接收者SUPPORT_INDEXED_ISA
條件,跳過__LP64__
條件,不知足needs_auth
等於0 的條件,進入else
分支ExtractISA p16, \src, \auth_address
:進入ExtractISA流程
ExtractISA
中會被賦值.macro ExtractISA
and $0, $1, #ISA_MASK
.endmacro
複製代碼
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
複製代碼
objc_msgSend
的後續流程
LGetIsaDone
流程CacheLookup
緩存查找流程,也就是所謂的sel-imp
快速查找流程objc_msgSend_uncached
--- 爲何要獲取類對象?
在快速查找流程中,查找的cache
存儲在類對象中,因此必須拿到類對象才能進行後面的流程