这是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:方法,它不会转发任何消息。这样,如果不在以上所述的三个步骤中处理未知消息,则会引发一个异常。
最后上一张图表示消息的派发和转发: