OC Runtime
OC Runtime 是一個 Runtime 庫喳挑,主要以 C 和匯編語言為基礎(chǔ)味赃,使用面向?qū)ο蟮?OC 來編寫掀抹。可以加載類心俗,對消息進行轉(zhuǎn)發(fā)傲武、分發(fā)等。
RunTime 庫是 Objective-C 面向?qū)ο蠛蛣討B(tài)機制的基石城榛。
這句話已經(jīng)充分說明了 RunTime 的重要性揪利。
消息傳遞
我們都知道,在編譯時 Objective-C
函數(shù)調(diào)用的語法都會被翻譯成 C
的函數(shù)調(diào)用 objc_msgSend()
吠谢,如下事例:
[obj doSomethingWithIndex:10];
等價于:
objc_msgSend(obj, @selector(doSomethingWithIndex:), 10);
重點在于 objc_object
中的 isa
指針和 objc_class
中的分發(fā)表 class dispatch table
objc_object 相關(guān)定義
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
objc_class 相關(guān)定義
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
objc_method 相關(guān)定義
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
};
struct objc_method_list {
struct objc_method_list * _Nullable obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
};
objc_method_list
是什么土童?
其本質(zhì)是一個有 objc_method
元素的可變長度的數(shù)組。
objc_method
結(jié)構(gòu)體中都有些什么工坊?
SEL _Nonnull method_name
: 函數(shù)名
char * _Nullable method_types
: 函數(shù)類型的字符串
IMP _Nonnull method_imp
: 函數(shù)的實現(xiàn)
通過以上献汗,不難看出,在 Objective-C
中王污,對象罢吃、類、方法都是一個 C
的結(jié)構(gòu)體昭齐。
到這里應(yīng)該能大致猜出 objc_msgSend()
到底做了哪些工作尿招。
例:objc_msgSend(obj, @selector(doSomethingWithIndex:), 10);
- 通過
obj
的isa
指針找到它的class
; - 在
class
的methodLists
中找doSomethingWithIndex:
; - 若
class
中未找到doSomethingWithIndex:
,前往super_class
中找; - 一旦找到
doSomethingWithIndex:
,立即執(zhí)行method_imp
就谜。
是不是每個消息都要按以上步驟把 objc_method_list
遍歷一遍怪蔑?
注意我們的 objc_class
中有一個成員 objc_cache
,那么它又是做什么的呢丧荐?
事實上缆瓣,在找到 doSomethingWithIndex:
方法后,將該方法的 method_name
作為 key 虹统,method_imp
作為 value 存起來弓坞。當(dāng)下次再收到 doSomethingWithIndex:
消息時,直接去 cache
里拿到车荔,并執(zhí)行 method_imp
渡冻。
消息轉(zhuǎn)發(fā)
平時開發(fā)中,我們都曾遇到過以下的錯誤提示:
unrecognized selector sent to instance
unrecognized selector sent to class
不用怕忧便, RunTime
會提供給我們?nèi)握鹊臋C會族吻。
第一次機會:
+ (BOOL)resolveInstanceMethod:(SEL)sel
或
+ (BOOL)resolveClassMethod:(SEL)sel
在這里,我們可以新添加一個函數(shù)實現(xiàn)并返回 YES
茬腿,運行時系統(tǒng)會重新啟動一次消息發(fā)送的過程呼奢。如下:
void addNewMethod(id self, SEL _cmd, NSString *str) {
NSLog(@"新添加的方法------%@", str);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(doSomeThing:)) {
class_addMethod(self, sel, (IMP)runTimeMethod, "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
注意:v@:@
具體什么含義,可查閱 Type Encodings 官方文檔
如果第一次機會中的方法返回為 NO
切平,此時會進行第二次機會握础。
第二次機會:
- (id)forwardingTargetForSelector:(SEL)aSelector
將對應(yīng)消息轉(zhuǎn)發(fā)給其他對象,只要返回的不是 nil
與 self
悴品,發(fā)送消息的過程會被重啟禀综,此時發(fā)送的對象變?yōu)榉祷氐哪莻€對象。如下:
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (sel == @selector(doSomeThing:)) {
return [[NewClass alloc] init];
}
}
第三次機會:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
獲得函數(shù)的參數(shù)和返回值類型
若返回為 nil
苔严,會發(fā)出
- (void)doesNotRecognizeSelector:(SEL)aSelector;
消息定枷,同時程序會掛掉
若返回函數(shù)簽名,RunTime
會創(chuàng)建 NSInvocation
對象届氢,并發(fā)送
- (void)forwardInvocation:(NSInvocation *)anInvocation
給目標(biāo)對象欠窒,如下:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(customMethod)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}else {
return [super methodSignatureForSelector:aSelector];
}
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
SEL another_selector = [anInvocation selector];
if ([another_object respondsToSelector:another_selector]) {
[anInvocation invokeWithTarget:[[NewClass alloc] init]];
}else {
[super forwardInvocation:anInvocation];
}
}