動態(tài)綁定
我們都知道OC是動態(tài)語言趁耗,表現(xiàn)為對象方法的調(diào)用實(shí)際上是對對象發(fā)送消息,編譯時不確定這個對象執(zhí)行什么方法疆虚,而在運(yùn)行時由消息(方法選擇器selector決定對象執(zhí)行什么方法)苛败,這種消息發(fā)送的方式叫做動態(tài)綁定。通過編譯我們知道径簿,發(fā)送消息的底層API是objc_msgSend
這個c函數(shù)罢屈,其工作流程大概是這樣的(為了方便參考,這里也給出了相關(guān)數(shù)據(jù)結(jié)構(gòu)的源碼):
// 類的數(shù)據(jù)結(jié)構(gòu)
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
// 方法的定義
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
- 獲取傳入對象所屬的類篇亭。
- 使用傳入的selector在緩存
cache
列表中查詢缠捌。 - 如果找到了方法,則通過
Method
中的IMP
(方法實(shí)現(xiàn)的地址)去調(diào)用方法译蒂。 - 如果緩存中不存在曼月,則開始在
methodLists
列表中查找。 - 如果
methodLists
列表找不到柔昼,利用super_class
指針去父類查找哑芹, - 遞歸地查找父類,一直查找到
NSObject
捕透,如果還是找不到聪姿,則觸發(fā)消息轉(zhuǎn)發(fā)機(jī)制碴萧。
消息轉(zhuǎn)發(fā)
簡單的說,就是當(dāng)對象通過selector找不到方法的時候末购,還有三次機(jī)會通過其他方式調(diào)用方法破喻,避免程序崩潰(unrecognized selector
),這三次機(jī)會分別是:
- 動態(tài)方法解析:看消息接受者是否能動態(tài)添加方法
- 備援的接收者:先看其他有沒有對象能處理這條消息
- 消息重定向:將所有信息封裝到
NSInvocation
對象中處理
動態(tài)方法解析盟榴,其實(shí)就是調(diào)用對象類中的以下方法:
+ (BOOL)resolveInstanceMethod:(SEL)selector; //當(dāng)遇到無法解讀的實(shí)例方法時調(diào)用這個方法
+ (BOOL)resolveClassMethod:(SEL)selector; //當(dāng)遇到無法解讀的類方法時調(diào)用這個方法
例子:
@interface Person : NSObject
@end
@implementation Person
- (void) implementAppendString:(NSString *)aString {
NSLog(@"%@", aString);
}
+ (BOOL) resolveInstanceMethod:(SEL)selName {
if (selName == @selector(speak:)) {
class_addMethod([self class], selName, class_getMethodImplementation([self class], @selector((implementSpeak:))), "v@:");
return YES;
}
return [super resolveInstanceMethod:selName];
}
@end
int main(int argc, const char *argv[])
{
@autoreleasepool {
id person = [[Person alloc] init];
[person performSelector: @selector(speak:) withObject:@"hello"];
}
return 0;
}
備援的接收者曹质,當(dāng)動態(tài)方法解析失敗時(返回NO
),可以通過以下方法詢問在其他對象中可否處理消息曹货,如果找到接收者咆繁,則返回接收者的對象讳推,否則返回nil
:
- (id)forwardingTargetForSelector:(SEL)aSelector;
例子:
@interface Person : NSObject
@end
@interface Father : NSObject
- (void)speak:(NSString *)aString;
@end
@implementation Person
- (id)forwardingTargetForSelector:(SEL)aSelector {
return [[Father alloc] init];
}
@end
@implementation Father
- (void)speak:(NSString *)aString {
NSLog(@"%@", aString);
}
@end
int main(int argc, const char *argv[])
{
@autoreleasepool {
id person = [[Person alloc] init];
[person performSelector: @selector(speak:) withObject:@"hello"]; // hello
}
return 0;
}
消息重定向顶籽,當(dāng)沒有備援接收者時,就只剩下最后一次機(jī)會银觅,那就是消息重定向礼饱。這個時候runtime
會將未知消息的所有細(xì)節(jié)都封裝為NSInvocation
對象,然后先后調(diào)用下述方法:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation: (NSInvocation*)invocation;
例子:
@interface Person : NSObject
@end
@implementation Person
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(speak)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return nil;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
if (invocation.selector == @selector(speak)) {
NSLog(@"%s", __func__);
}
}
@end
int main(int argc, const char *argv[])
{
@autoreleasepool {
id person = [[Person alloc] init];
[person performSelector: @selector(speak)]; // -[Person forwardInvocation:]
}
return 0;
}