通常,我們以[object message]
這種方式向一個類或者對象發送一個未定義消息時,在Xcode編譯的時候就會報錯。但是,如果出現以下情況時就會進入方法轉發流程:
1.我們使用[object message]
向一個對象發送了只有message
定義沒有實現的消息。
2.使用[object performSelector:@selector(message)]
向一個未定義(或者未實現)message
的對象發送消息時就會進入方法轉發流程。
總之就是object
無法響應message
消息。一般如果出現無法響應message
消息,系統就會崩潰并報出unrecognized selector sent to instance
錯誤。但是如何不讓系統直接崩潰了?那么就進入我們下面討論的消息轉發流程。
首先讓我們來了解幾個概覽:
一.類和元類(Meta Class)
1.類
Objective-C類是由Class
類型來表示的,它實際上是一個指向objc_class
結構體的指針,讓我們來看一下它的結構
typedef struct objc_class *Class;
查看objc/runtime.h
中objc_class
結構體的定義如下
struct objc_class {
Class isa;
#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;
- isa:在object-c中,一個類也是一個對象(類對象),這個類的isa指向它的元類。
- ivars:存放當前類的成員變量列表。
- methodLists:方法列表,存放當前類的所有方法。
2.元類(Meta Class)
其實一個類也是一個對象,當我們給一個類發送一個消息的時候,比如[NSArray array]
的時候就是給NSArray
類對象發送array
消息。這個類的isa
指針指向包含這個類的objc_class
結構體。
類對象所屬類型就叫做元類,它用來表述類對象本身所具備的元數據。具體的結構可以查看下圖:
二.隱式參數
在objc_msgSend
方法中有兩個隱式參數,這也是為什么我們可以在方法中調用self
的原因。兩個隱藏參數如下:
- 接受消息的對象(self)
- 方法選擇器(_cmd指向的內容)
之所以說是隱藏的,是因為它們在定義方法的源代碼中沒有聲明。它們是在編譯期被插入實現代碼的。
三.消息轉發
1.動態方法解析
當我們實現了+resolveInstanceMethod
或者+resolveClassMethod
方法 ,如果對象或者類無法處理所接受的消息的時候就會首先進入這一步。
創建一個類,并使用performSelector
調用一個不存在的方法,并使用如下方法進入消息轉發流程
// 拯救了一個實例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(myInstanceMethod)) {
class_addMethod(self, sel, (IMP) funcInstanceM, "@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void funcInstanceM(id self, SEL _cmd) {
NSLog(@"%@, %p", self, _cmd);
}
funcInstanceM
函數包含必須添加的兩個隱藏參數self
和_cmd
。
但是如果我們給MyClass
發送一個無法響應的類方法的時候,怎么進行動態方法解析了?
我們可以使用+resolveClassMethod
進行消息轉發
[MyClass performSelector:@selector(myClassMethod)];
+ (BOOL)resolveClassMethod:(SEL)sel
{
if (sel == @selector(myClassMethod)) {
class_addMethod(object_getClass(self), sel, (IMP) funcClassM, "@:");
return YES;
}
return [super resolveClassMethod:sel];
}
void funcClassM(id self, SEL _cmd) {
NSLog(@"%@, %p", self, _cmd);
}
這里需要注意的是,我們使用object_getClass
獲取當前類的元類。
2.備用接受者
如果上述方法無法處理,那就使用- forwardingTargetForSelector
將消息轉發給能夠處理的對象
// MyClass 定義
@interface MyClass : NSObject
- (void)method;
@end
@implementation MyClass
- (void)method{
NSLog(@"%@, %p", self, _cmd);
}
@end
// MyRuntimeClass定義
@interface MyRuntimeClass : NSObject
@property (nonatomic, strong) MyClass *myClass;
- (void)test;
@end
@implementation MyRuntimeClass
- (instancetype)init
{
if (self = [super init]) {
self.myClass = [MyClass new];
}
return self;
}
- (void)test
{
[self performSelector:@selector(method)];
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(method)) {
return self.myClass;
}
return [super forwardingTargetForSelector:aSelector];
}
可以在外界調用MyRuntimeClass
的test
測試。
如果此方法返回nil或self,則會進入完整方法解析(forwardInvocation:);否則將向返回的對象重新發送消息。
可以使用+ forwardingTargetForSelector:
返回一個類對象進行類方法的轉發
3.完整方法轉發
如果還沒有使用上述方法處理,那么將會使用- forwardInvocation:
進行最后的消息轉發
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if ([MyClass instancesRespondToSelector:aSelector]) {
signature = [MyClass instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
// 返回一個BOOL值用來判斷消息接受者實例能否相應給定的消息
if ([MyClass instancesRespondToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:self.myClass];
}
}
Runtime系統會向對象發送- methodSignatureForSelector:
消息,并取到返回的方法簽名用于生成NSInvocation對象。因此我們在使用- (void)forwardInvocation:
進行消息轉發時必須實現- methodSignatureForSelector:
。
如果上述方法你都沒有處理,那么只有崩潰并報unrecognized selector sent to instance
錯誤。
參考鏈接:
http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/
http://southpeak.github.io/2014/11/03/objective-c-runtime-3/