這是OC運行時實戰應用系列的第二篇,你可以在這里找到實戰應用1,這一片主要從消息發送,消息轉發,消息交換的角度講解相關應用。
Objective-C 是一門動態語言,它的動態性體現在它將很多編譯和鏈接時做的事推延到運行時處理,而這一機制主要依賴系統提供的 runtime 庫。利用 runtime 庫,我們能在運行時做很多事,例如 objc_setAssociatedObject 動態綁定屬性、method swizzling、class_copyIvarList 動態獲取屬性實現 ORM(Object Relational Mapping)、消息轉發等,本文先解析消息轉發機制。
幾個概念
- Class
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;
作為面向對象編程語言的最重要的數據結構--類(Class),其實是一個C語言的結構體,這個結構體里面封裝了描繪這個類所有信息。
//參數說明:
Class _Nonnull isa 一個指向類的結構體的指針,在objc中,根據對象的定義,凡是首地址是*isa的結構體指針,都可以認為是對象(id),所以類本身也是對象,它的isa指針指向它的源類,源類的isa指向根類,根類的isa指向本身。
Class _Nullable super_class 這也是一個指向類的結構體的指針,不過它指向這個類的父類,通過這個字段類之間形成了繼承關系
const char * _Nonnull name 類名
long version 類的版本信息,默認為0
long info 供運行期使用的一些位標識
long instance_size 該類的實例變量大小
struct objc_ivar_list * _Nullable ivars 成員變量的數組的指針
struct objc_method_list * _Nullable * _Nullable methodLists 方法定義的數組的二級指針
struct objc_cache * _Nonnull cache 指向最近使用的方法.用于方法調用的優化.
struct objc_protocol_list * _Nullable protocols 指向協議的數組的指針
總之這個結構體里面包含了面向對象程序的幾大要素,對象與類的關系,繼承體系,成員變量,成員方法,接口,以及用于函數調用緩存的cache。
2.Object
OC的對象其實也是包含一個isa指針的結構體
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
3.SEL
可以理解為將方法名,參數列表,返回值進行hash化了的,在一個類里唯一存在的字符串鍵值,用來唯一標識一個函數
typedef struct objc_selector *SEL;
4.IMP
函數指針,可以用來調取函數體
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
5.Other
以下都是對一個結構體類型的封裝,用在runtime庫里的數據類型,我們在調用運行時API的時候會用到
typedef struct objc_class *Class;
typedef struct objc_object *id;
typedef struct objc_method *Method;
typedef struct objc_ivar *Ivar;
typedef struct objc_category *Category;
typedef struct objc_property *objc_property_t;
typedef struct objc_object Protocol;
typedef struct objc_cache *Cache
typedef struct objc_module *Module
消息派發
[receiver message];
這是OC調用方法的寫法,向receiver發送message消息。
clang -rewrite-objc MyClass.m
用 clang 的命令將 OC 的語法裝換成 C 的語法,是這樣的:
((void (*)(id, SEL))(void *)objc_msgSend)((id)receiver, sel_registerName("message"));
簡化之后變成了下面的 C 語言的調用:
objc_msgSend(receiver, @selector(message));
所以說,objc發送消息,最終大都會轉換為objc_msgSend的方法調用。
所以OC方法的調用過程大致是這樣的:首先在Class中的緩存查找imp(沒緩存則初始化緩存),如果沒找到,則向父類的Class查找。如果一直查找到根類仍舊沒有實現,則用_objc_msgForward函數指針代替imp。最后,執行這個imp。
消息轉發
當向一個對象發送一條消息,但它并沒有實現的時候,_objc_msgForward會嘗試做消息轉發。
1.在本類中找其他方法
調用resolveInstanceMethod:方法,允許用戶在此時為該Class動態添加實現。如果有實現了,則調用并返回。如果仍沒實現,繼續下面的動作。
Test *t = [[Test alloc] init];
[t performSelector:@selector(xxx)];
void additionalMethod_01(id self, SEL _cmd) {
NSLog(@"%@, %p", self, _cmd);
}
//動態添加方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *selectorString = NSStringFromSelector(sel);
if ([selectorString isEqualToString:@"xxx"]) {
class_addMethod(self.class, @selector(xxx), (IMP)additionalMethod_01, "@:");
}
return [super resolveInstanceMethod:sel];
}
- (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);//動態添加實例方法
- (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);//動態添加類方法
2.嘗試找到一個能響應該消息的對象
調用forwardingTargetForSelector:方法,嘗試找到一個能響應該消息的對象。如果獲取到,則直接轉發給它。如果返回了nil,繼續下面的動作。
@interface MethodHelper : NSObject
- (void)xxx;
@end
#import "MethodHelper.h"
@implementation MethodHelper
- (void)xxx {
NSLog(@"%s",__func__);
}
@end
@interface Test : NSObject
@property (strong, nonatomic) MethodHelper *helper;
@end
@implementation Test
- (instancetype)init {
self = [super init];
if (self != nil) {
_helper = [[MethodHelper alloc] init];
}
return self;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s",__func__);
if ([NSStringFromSelector(aSelector) isEqualToString:@"xxx"]) {
return _helper;
}
return [super forwardingTargetForSelector:aSelector];
}
@end
Test *t = [[Test alloc] init];
[t performSelector:@selector(xxx)];
這樣就把消息轉發給另一個能處理這個消息的對象。
3.調用methodSignatureForSelector:方法,嘗試獲得一個方法簽名。如果獲取不到,則直接調用doesNotRecognizeSelector拋出異常。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if ([MethodHelper instancesRespondToSelector:aSelector]) {
signature = [MethodHelper instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
4.調用forwardInvocation:方法,將地3步獲取到的方法簽名包裝成Invocation傳入,如何處理就在這里面了
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([MethodHelper instanceMethodSignatureForSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:_helper];
}
}
上面這4個方法均是模板方法,開發者可以override,由runtime來調用。最常見的實現消息轉發,就是重寫方法3和4,吞掉一個消息或者代理給其他對象都是沒問題的。
NSObject的forwardInvocation:方法實現只是簡單調用了doesNotRecognizeSelector:方法,它不會轉發任何消息。這樣,如果不在以上所述的三個步驟中處理未知消息,則會引發一個異常。
最后上一張圖表示消息的派發和轉發:
