這是OC運行時實戰(zhàn)應用系列的第二篇盘榨,你可以在這里找到實戰(zhàn)應用1,這一片主要從消息發(fā)送解总,消息轉(zhuǎn)發(fā)宇葱,消息交換的角度講解相關(guān)應用筷黔。
Objective-C 是一門動態(tài)語言往史,它的動態(tài)性體現(xiàn)在它將很多編譯和鏈接時做的事推延到運行時處理,而這一機制主要依賴系統(tǒng)提供的 runtime 庫佛舱。利用 runtime 庫椎例,我們能在運行時做很多事,例如 objc_setAssociatedObject 動態(tài)綁定屬性请祖、method swizzling订歪、class_copyIvarList 動態(tài)獲取屬性實現(xiàn) ORM(Object Relational Mapping)、消息轉(zhuǎn)發(fā)等肆捕,本文先解析消息轉(zhuǎn)發(fā)機制刷晋。
幾個概念
- 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;
作為面向?qū)ο缶幊陶Z言的最重要的數(shù)據(jù)結(jié)構(gòu)--類(Class),其實是一個C語言的結(jié)構(gòu)體慎陵,這個結(jié)構(gòu)體里面封裝了描繪這個類所有信息眼虱。
//參數(shù)說明:
Class _Nonnull isa 一個指向類的結(jié)構(gòu)體的指針,在objc中席纽,根據(jù)對象的定義捏悬,凡是首地址是*isa的結(jié)構(gòu)體指針,都可以認為是對象(id)润梯,所以類本身也是對象过牙,它的isa指針指向它的源類,源類的isa指向根類纺铭,根類的isa指向本身寇钉。
Class _Nullable super_class 這也是一個指向類的結(jié)構(gòu)體的指針,不過它指向這個類的父類舶赔,通過這個字段類之間形成了繼承關(guān)系
const char * _Nonnull name 類名
long version 類的版本信息扫倡,默認為0
long info 供運行期使用的一些位標識
long instance_size 該類的實例變量大小
struct objc_ivar_list * _Nullable ivars 成員變量的數(shù)組的指針
struct objc_method_list * _Nullable * _Nullable methodLists 方法定義的數(shù)組的二級指針
struct objc_cache * _Nonnull cache 指向最近使用的方法.用于方法調(diào)用的優(yōu)化.
struct objc_protocol_list * _Nullable protocols 指向協(xié)議的數(shù)組的指針
總之這個結(jié)構(gòu)體里面包含了面向?qū)ο蟪绦虻膸状笠兀瑢ο笈c類的關(guān)系顿痪,繼承體系镊辕,成員變量,成員方法蚁袭,接口征懈,以及用于函數(shù)調(diào)用緩存的cache。
2.Object
OC的對象其實也是包含一個isa指針的結(jié)構(gòu)體
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
3.SEL
可以理解為將方法名揩悄,參數(shù)列表卖哎,返回值進行hash化了的,在一個類里唯一存在的字符串鍵值,用來唯一標識一個函數(shù)
typedef struct objc_selector *SEL;
4.IMP
函數(shù)指針亏娜,可以用來調(diào)取函數(shù)體
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
5.Other
以下都是對一個結(jié)構(gòu)體類型的封裝焕窝,用在runtime庫里的數(shù)據(jù)類型,我們在調(diào)用運行時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
消息派發(fā)
[receiver message];
這是OC調(diào)用方法的寫法,向receiver發(fā)送message消息。
clang -rewrite-objc MyClass.m
用 clang 的命令將 OC 的語法裝換成 C 的語法维贺,是這樣的:
((void (*)(id, SEL))(void *)objc_msgSend)((id)receiver, sel_registerName("message"));
簡化之后變成了下面的 C 語言的調(diào)用:
objc_msgSend(receiver, @selector(message));
所以說它掂,objc發(fā)送消息,最終大都會轉(zhuǎn)換為objc_msgSend的方法調(diào)用溯泣。
所以O(shè)C方法的調(diào)用過程大致是這樣的:首先在Class中的緩存查找imp(沒緩存則初始化緩存)虐秋,如果沒找到,則向父類的Class查找垃沦。如果一直查找到根類仍舊沒有實現(xiàn)客给,則用_objc_msgForward函數(shù)指針代替imp。最后肢簿,執(zhí)行這個imp靶剑。
消息轉(zhuǎn)發(fā)
當向一個對象發(fā)送一條消息,但它并沒有實現(xiàn)的時候池充,_objc_msgForward會嘗試做消息轉(zhuǎn)發(fā)桩引。
1.在本類中找其他方法
調(diào)用resolveInstanceMethod:方法,允許用戶在此時為該Class動態(tài)添加實現(xiàn)纵菌。如果有實現(xiàn)了阐污,則調(diào)用并返回。如果仍沒實現(xiàn)咱圆,繼續(xù)下面的動作笛辟。
Test *t = [[Test alloc] init];
[t performSelector:@selector(xxx)];
void additionalMethod_01(id self, SEL _cmd) {
NSLog(@"%@, %p", self, _cmd);
}
//動態(tài)添加方法
+ (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);//動態(tài)添加實例方法
- (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);//動態(tài)添加類方法
2.嘗試找到一個能響應該消息的對象
調(diào)用forwardingTargetForSelector:方法,嘗試找到一個能響應該消息的對象序苏。如果獲取到手幢,則直接轉(zhuǎn)發(fā)給它。如果返回了nil忱详,繼續(xù)下面的動作围来。
@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)];
這樣就把消息轉(zhuǎn)發(fā)給另一個能處理這個消息的對象。
3.調(diào)用methodSignatureForSelector:方法匈睁,嘗試獲得一個方法簽名监透。如果獲取不到,則直接調(diào)用doesNotRecognizeSelector拋出異常航唆。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if ([MethodHelper instancesRespondToSelector:aSelector]) {
signature = [MethodHelper instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
4.調(diào)用forwardInvocation:方法胀蛮,將地3步獲取到的方法簽名包裝成Invocation傳入,如何處理就在這里面了
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([MethodHelper instanceMethodSignatureForSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:_helper];
}
}
上面這4個方法均是模板方法糯钙,開發(fā)者可以override粪狼,由runtime來調(diào)用退腥。最常見的實現(xiàn)消息轉(zhuǎn)發(fā),就是重寫方法3和4再榄,吞掉一個消息或者代理給其他對象都是沒問題的狡刘。
NSObject的forwardInvocation:方法實現(xiàn)只是簡單調(diào)用了doesNotRecognizeSelector:方法,它不會轉(zhuǎn)發(fā)任何消息困鸥。這樣嗅蔬,如果不在以上所述的三個步驟中處理未知消息,則會引發(fā)一個異常窝革。
最后上一張圖表示消息的派發(fā)和轉(zhuǎn)發(fā):
