一锐锣、Runtime
runtime
稱之為運行時
,與之相對的是編譯時
運行時
著洼,是代碼跑起來樟遣,被裝載到內存中
的過程,是動態(tài)
階段身笤,此時出錯會導致程序崩潰
編譯時
豹悬,是源代碼翻譯成機器能識別的代碼
的過程,是靜態(tài)
階段液荸,主要做一些詞法分析
瞻佛,語法分析
等操作
二、Runtime調用的三種途徑
runtime調用的三種途徑
三娇钱、objc_msgSend
一伤柄、方法的本質
在isa結構分析中,我們通過Clang
查看了對象的本質
文搂,同樣的适刀,這里我們一樣可以使用Clang
查看方法的本質
//main函數(shù)中方法的調用
LGPerson *person = [LGPerson alloc];
[person sayHello];
//clang后的方法的調用
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("sayHello"));
可以看出,方法的本質
是objc_msgSend(消息發(fā)送)
這里我們驗證一下细疚,它們是不是真的一樣:
1蔗彤、導入頭文件<objc/message.h>
2川梅、設置enable strict checking of obc_msgSend calls
為NO
(否則objc_msgSend的參數(shù)會報錯)
LGPerson *person = [LGPerson alloc];
//1.方法的調用
[person sayHello];
//2.消息發(fā)送
objc_msgSend(person, sel_registerName("sayHello"));
打印結果如圖:
方法調用與objc_msgSend打印結果
可以看到,打印結果是一樣的然遏,所以
[person sayHello]
等價于objc_msgSend(person, sel_registerName("sayHello"))
二贫途、方法的調用,會執(zhí)行父類的實現(xiàn)
1待侵、定義兩個類LGTeacher
丢早、LGPerson
@interface LGTeacher : NSObject
- (void)sayHello;
@end
@implementation LGTeacher
- (void)sayHello {
NSLog(@"666");
}
@end
@interface LGPerson : LGTeacher
- (void)sayHello;
- (void)sayNB;
@end
@implementation LGPerson
- (void)sayNB {
NSLog(@"666");
}
@end
可以看到,LGPerson
只聲明了sayHello
方法卻沒有實現(xiàn)秧倾,但是父類LGTeacher
實現(xiàn)了
2怨酝、通過objc_msgSendSuper
調用父類方法
LGPerson *person = [LGPerson alloc];
LGTeacher *teacher = [LGTeacher alloc];
[person sayHello];
struct objc_super lgsuper;
lgsuper.receiver = person;//消息的接收者還是person
lgsuper.super_class = [LGTeacher class];//告訴父類是誰
////消息的接受者還是自己 - 父類 - 請你直接找我的父親
objc_msgSendSuper(&lgsuper, sel_registerName("sayHello"));
objc_msgSendSuper
方法中有兩個參數(shù):
OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
#endif
結構體 objc_super
和sel
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
/* super_class is the first class to search */
};
#endif
結構體
中需要指定receiver
和super_class
3、打印結果如圖:
方法的調用那先,會執(zhí)行父類的實現(xiàn)
可以看出农猬,無論是
[person sayHello]
還是objc_msgSendSuper(&lgsuper, sel_registerName("sayHello"))
執(zhí)行的都是父類
中的實現(xiàn)
三、初探objc_msgSend
打開源碼搜索objc_msgSend
售淡,選擇arm64.s
后綴的文件斤葱,可以查找objc_msgSend
的源碼實現(xiàn),發(fā)現(xiàn)是匯編實現(xiàn)
//---- 消息發(fā)送 -- 匯編入口--objc_msgSend主要是拿到接收者的isa信息
ENTRY _objc_msgSend
//---- 無窗口
UNWIND _objc_msgSend, NoFrame
//---- p0 和空對比揖闸,即判斷接收者是否存在揍堕,其中p0是objc_msgSend的第一個參數(shù)-消息接收者receiver
cmp p0, #0 // nil check and tagged pointer check
//---- le小于 --支持taggedpointer(小對象類型)的流程
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
//---- p0 等于 0 時,直接返回 空
b.eq LReturnZero
#endif
//---- p0即receiver 肯定存在的流程
//---- 根據(jù)對象拿出isa 汤纸,即從x0寄存器指向的地址 取出 isa衩茸,存入 p13寄存器
ldr p13, [x0] // p13 = isa
//---- 在64位架構下通過 p16 = isa(p13) & ISA_MASK,拿出shiftcls信息贮泞,得到class信息
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
// calls imp or objc_msgSend_uncached
//---- 如果有isa楞慈,走到CacheLookup 即緩存查找流程,也就是所謂的sel-imp快速查找流程
CacheLookup NORMAL, _objc_msgSend
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
//---- 等于空隙畜,返回空
b.eq LReturnZero // nil check
// 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
END_ENTRY _objc_msgSend
整體執(zhí)行的流程如圖:
objc_msgSend流程圖