Runtime简单使用二
通常,我们以[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/