美文网首页
Runtime简单使用二

Runtime简单使用二

作者: hj的简书 | 来源:发表于2016-09-08 15:49 被阅读0次

    通常,我们以[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;
    
    • 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];
    }
    
    

    可以在外界调用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/

    相关文章

      网友评论

          本文标题:Runtime简单使用二

          本文链接:https://www.haomeiwen.com/subject/suwjettx.html