Runtime简单使用二

2016-09-08  本文已影响0人  hj的简书

通常,我们以[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.hobjc_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;

2.元类(Meta Class)

其实一个类也是一个对象,当我们给一个类发送一个消息的时候,比如[NSArray array]的时候就是给NSArray类对象发送array消息。这个类的isa指针指向包含这个类的objc_class结构体。

类对象所属类型就叫做元类,它用来表述类对象本身所具备的元数据。具体的结构可以查看下图:

类、元类的关系

二.隐式参数

objc_msgSend方法中有两个隐式参数,这也是为什么我们可以在方法中调用self的原因。两个隐藏参数如下:

之所以说是隐藏的,是因为它们在定义方法的源代码中没有声明。它们是在编译期被插入实现代码的。

三.消息转发

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];
}

可以在外界调用MyRuntimeClasstest测试。
如果此方法返回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/

上一篇下一篇

猜你喜欢

热点阅读