ios浅谈runtime

作者: 死不了的猫 | 来源:发表于2016-08-01 00:09 被阅读295次

    老样子..先看文档 知道 runtime 到底是个什么东西呢.....

    The Objective-C language defers as many decisions as it can from compile time and link time to runtime. Whenever possible, it does things dynamically. This means that the language requires not just a compiler, but also a runtime system to execute the compiled code. The runtime system acts as a kind of operating system for the Objective-C language; it’s what makes the language work.

    runtime 使objc变成了真正的动态语言,是objc 的操作系统.objc 延迟了许多决定,直到运行时才做出.runtime 给了 objc 真正的动态性

    runtime 运行时有两个版本,一般手机和64位机器的用的是现代版本,一般32位的机器用的是古老版本..我们接下来都是以现代版本进行解释的

    和 objc-runtime 交互有三种方式

    1. 通过 objc 源代码 我们在写 objc 源代码的时候,在编译期间有的就已经被翻译优化了.背后变成了调用运行时的代码
    2. 就是通过 nsobject 中的一些方法 nsobject 不规定类的具体行为.只定义了它们的基本必须结构
    3. 最后的,自然就是通过runtime函数库直接调用

    how the message expressions are converted into objc_msgSend function calls, and how you can refer to methods by name. It then explains how you can take advantage of objc_msgSend, and how—if you need to—you can circumvent dynamic binding.

    如果你对objc消息机制掌握的很好,完全可以绕过动态绑定实现黑魔法
    消息的最终接收者都是在运行时才确定的

    [receiver message]
    编译器转换为:
    objc_msgSend(receiver, selector) //两个必须参数
    objc_msgSend(receiver, selector, arg1, arg2, ...)//之后可选参数
    

    The messaging function does everything necessary for dynamic binding:

    It first finds the procedure (method implementation) that the selector refers to. Since the same method can be implemented differently by separate classes, the precise procedure that it finds depends on the class of the receiver.
    It then calls the procedure, passing it the receiving object (a pointer to its data), along with any arguments that were specified for the method.
    Finally, it passes on the return value of the procedure as its own return value.

    作用:根据message 和 receiver的类的找到唯一的方法

    我们先来看看类是什么 objc-class

     struct objc_class {
     Class isa  OBJC_ISA_AVAILABILITY;                       //指向元类
    
    #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;
    /* Use `Class` instead of `struct objc_class *` */
    
    typedef struct objc_class *Class;
    

    这是需要了解的东西 一个实例中都有一个 isa 指针,指向一个 Class(objc_class*)的结构体的指针,也就是实例指向自己的类,类中也有一个 Class(isa)指向元类.一切皆对象中,类也是对象

    类与对象操作函数

    runtime提供了大量的函数来操作类与对象。类的操作方法大部分是以class为前缀的,而对象的操作方法大部分是以objc或object_为前缀。

    When objc_msgSend finds the procedure that implements a method, it calls the procedure and passes it all the arguments in the message. It also passes the procedure two hidden arguments:

    The receiving object
    The selector for the method
    These arguments give every method implementation explicit information about the two halves of the message expression that invoked it. They’re said to be “hidden” because they aren’t declared in the source code that defines the method. They’re inserted into the implementation when the code is compiled.

    每底层objc_msgsend的方法在运行时都会有两个隐含的参数 在编译时被自动添加 一个是 self.指向receiver 一个是_cmd..代表的是方法本身
    下面官方代码测试这两个参数

       - strange
     {
        id  target = getTheReceiver();
        SEL method = getTheMethod();
        
        if ( target == self || method == _cmd )
            return nil;
        return [target performSelector:method];
    } 
    

    self 很有用啊,我们不是经常通过 self 获得消息和成员变量嘛

    每次执行方法的时候,都需要在调度表中查询找到函数的入口地址,这对于一般方法没问题.但是对于需要超高效率和执行次数多的方法,我们可以直接跳过动态绑定

    sing methodForSelector: to circumvent dynamic binding saves most of the time required by messaging. However, the savings will be significant only where a particular message is repeated many times, as in the for loop shown above.

    我们一般用 methodForSelector 来跳过动态绑定这]法是得到 selector 的实际函数

    | SEL1 | SEL2 | SEL3 |

    | IMP1 | IMP2 | IMP3 |

    sel 只是一个方法的标志符,真正的执行代码地址是 imp 指向的地方 methodforselector可以的某个方法标识符的地址,我们也可以在程序中之直接使用

    IMP imp =  [ NSObject methodForSelector:@selector(class)];
    imp();
    
    
    typedef id (*IMP)(id, SEL,... );
    

    IMP is a C type referring to the implementation of a method, also known as an implementation pointer. It's a pointer to a function returning id, and with self and a method selector (available inside method definitions as the variable _cmd) as the first arguments

    可以看到 id 是一种数据类型,imp 是一个指向返回数据类型是 id 的函数的指针,这个函数的第一个参数和第二个参数是self 和这个函数名_cmd;

    objc 将许多需要编译和链接时确定的延迟到运行时确定,这样就发生了许多有趣的东西.比如.我们可以动态的给类添加属性使用 @dynamic propertyName;
    动态的告诉编译器消息使用的属性

    我们也可以动态的给类添加方法.(所有的这些和 java 中因为有虚拟机而有的反射有异曲同工之妙)

     void dynamicMethodIMP(id self, SEL _cmd) {
    // implementation ....
    }
    @implementation MyClass
    + (BOOL)resolveInstanceMethod:(SEL)aSEL
    {
        if (aSEL == @selector(resolveThisMethodDynamically)) {
              class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
              return YES;
        }
        return [super resolveInstanceMethod:aSEL];
    }
    

    class_addMethod 接收的参数:要添加的类 ,方法标识符 sel;方法执行入口地址;描述符

    我们一定要有一个意识(runtime 是 一个函数库 帮助我们更好的处理 objc 和 java 虚拟机做的事情是很类似的 这是动态语言的一个大特性)

    消息转发
    当一个对象能接收一个消息时,就会走正常的方法调用流程。但如果一个对象无法接收指定消息时,又会发生什么事呢?默认情况下,如果是以 [object message]的方式调用方法,如果object无法响应message消息时,编译器会报错。但如果是以perform…的形式来调用,则需要等到运 行时才能确定object是否能接收message消息。如果不能,则程序崩溃。
    所以我们不能确定一个对象是否可以接受一个消息的时候,会先进行判断

    if ([self respondsToSelector:@selector(method)]) {
    [self performSelector:@selector(method)];
    }
    

    当一个对象无法接收某一消息时,就会启动所谓”消息转发(message forwarding)“机制,通过这一机制,我们可以告诉对象如何处理未知的消息。默认情况下,对象接收到未知的消息,会导致程序崩溃,报错

    untime的强大之处在于它能在运行时创建类和对象。

    动态创建类

    动态创建类涉及到以下几个函数:

    // 创建一个新类和元类

    Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes );
    

    // 销毁一个类及其相关联的类

    void objc_disposeClassPair ( Class cls );
    

    // 在应用中注册由objc_allocateClassPair创建的类

    void objc_registerClassPair ( Class cls );
    

    和 java 中的反射有异曲同工之妙

    息转发机制基本上分为三个步骤:

    1. 动态方法解析

    2. 备用接收者

    3. 完整转发

    下面我们详细讨论一下这三个步骤。

    动态方法解析

    对象在接收到未知的消息时,首先会调用所属类的类方法+resolveInstanceMethod:(实例方法)或 者+resolveClassMethod:(类方法)。在这个方法中,我们有机会为该未知消息新增一个”处理方法”“。不过使用该方法的前提是我们已经 实现了该”处理方法”,只需要在运行时通过class_addMethod函数动态添加到类里面就可以了。如下代码所示:

    void functionForMethod1(id self, SEL _cmd) {
       NSLog(@"%@, %p", self, _cmd);
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel {
    
    NSString *selectorString = NSStringFromSelector(sel);
    
    if ([selectorString isEqualToString:@"method1"]) {
        class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:");
    }
    
    return [super resolveInstanceMethod:sel];
    }
    

    不过这种方案更多的是为了实现@dynamic属性。

    备用接收者

    如果在上一步无法处理消息,则Runtime会继续调以下方法:

    • (id)forwardingTargetForSelector:(SEL)aSelector
      如果一个对象实现了这个方法,并返回一个非nil的结果,则这个对象会作为消息的新接收者,且消息会被分发到这个对象。当然这个对象不能是self自身,否则就是出现无限循环。当然,如果我们没有指定相应的对象来处理aSelector,则应该调用父类的实现来返回结果。

    使用这个方法通常是在对象内部,可能还有一系列其它对象能处理该消息,我们便可借这些对象来处理消息并返回,这样在对象外部看来,还是由该对象亲自处理了这一消息。如下代码所示:

    @interface SUTRuntimeMethodHelper : NSObject
    
    - (void)method2;
    
    @end
    
    @implementation SUTRuntimeMethodHelper
    
    - (void)method2 {
        NSLog(@"%@, %p", self, _cmd);
    }
    
    @end
    
    #pragma mark -
    
    @interface SUTRuntimeMethod () {
        SUTRuntimeMethodHelper *_helper;
    }
    
    @end    
    
    @implementation SUTRuntimeMethod
    
    + (instancetype)object {
        return [[self alloc] init];
    }
    
    - (instancetype)init {
        self = [super init];
        if (self != nil) {
            _helper = [[SUTRuntimeMethodHelper alloc] init];
        }
    
        return self;
    }
    
    - (void)test {
        [self performSelector:@selector(method2)];
    }
    
    - (id)forwardingTargetForSelector:(SEL)aSelector {
    
        NSLog(@"forwardingTargetForSelector");
    
        NSString *selectorString = NSStringFromSelector(aSelector);
    
    // 将消息转发给_helper来处理
    if ([selectorString isEqualToString:@"method2"]) {
        return _helper;
    }
    
        return [super forwardingTargetForSelector:aSelector];
    }
    
    @end
    

    这一步合适于我们只想将消息转发到另一个能处理该消息的对象上。但这一步无法对消息进行处理,如操作消息的参数和返回值。

    完整消息转发

    如果在上一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了。此时会调用以下方法:

    - (void)forwardInvocation:(NSInvocation *)anInvocation
    

    运行时系统会在这一步给消息接收者最后一次机会将消息转发给其它对象。对象会创建一个表示消息的NSInvocation对象,把与尚未处理的消息 有关的全部细节都封装在anInvocation中,包括selector,目标(target)和参数。我们可以在forwardInvocation 方法中选择将消息转发给其它对象。

    forwardInvocation:方法的实现有两个任务:

    1. 定位可以响应封装在anInvocation中的消息的对象。这个对象不需要能处理所有未知消息。

    2. 使用anInvocation作为参数,将消息发送到选中的对象。anInvocation将会保留调用结果,运行时系统会提取这一结果并将其发送到消息的原始发送者。

    不过,在这个方法中我们可以实现一些更复杂的功能,我们可以对消息的内容进行修改,比如追回一个参数等,然后再去触发消息。另外,若发现某个消息不应由本类处理,则应调用父类的同名方法,以便继承体系中的每个类都有机会处理此调用请求。

    还有一个很重要的问题,我们必须重写以下方法:

    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    

    消息转发机制使用从这个方法中获取的信息来创建NSInvocation对象。因此我们必须重写这个方法,为给定的selector提供一个合适的方法签名。

    完整的示例如下所示:

    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
    
    if (!signature) {
        if ([SUTRuntimeMethodHelper instancesRespondToSelector:aSelector]) {
            signature = [SUTRuntimeMethodHelper instanceMethodSignatureForSelector:aSelector];
        }
    }
    
    return signature;
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        if ([SUTRuntimeMethodHelper             instancesRespondToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:_helper];
    }
    }
    

    NSObject的forwardInvocation:方法实现只是简单调用了doesNotRecognizeSelector:方法,它不会转发任何消息。这样,如果不在以上所述的三个步骤中处理未知消息,则会引发一个异常。

    从某种意义上来讲,forwardInvocation:就像一个未知消息的分发中心,将这些未知的消息转发给其它对象。或者也可以像一个运输站一样将所有未知消息都发送给同一个接收对象。这取决于具体的实现。

    消息转发与多重继承

    回过头来看第二和第三步,通过这两个方法我们可以允许一个对象与其它对象建立关系,以处理某些未知消息,而表面上看仍然是该对象在处理消息。通过这 种关系,我们可以模拟“多重继承”的某些特性,让对象可以“继承”其它对象的特性来处理一些事情。不过,这两者间有一个重要的区别:多重继承将不同的功能 集成到一个对象中,它会让对象变得过大,涉及的东西过多;而消息转发将功能分解到独立的小的对象中,并通过某种方式将这些对象连接起来,并做相应的消息转 发。

    不过消息转发虽然类似于继承,但NSObject的一些方法还是能区分两者。如respondsToSelector:和isKindOfClass:只能用于继承体系,而不能用于转发链。便如果我们想让这种消息转发看起来像是继承,则可以重写这些方法,如以下代码所示:

    - (BOOL)respondsToSelector:(SEL)aSelector   {
       if ( [super respondsToSelector:aSelector] )
                return YES;     
       else {
                 /* Here, test whether the aSelector message can
                  *            
                  * be forwarded to another object and whether that  
                  *            
                  * object can respond to it. Return YES if it can.  
                  */      
       }
       return NO;  
    }
    

    这些东西我们只要了解就好,在开发中用到的不多

    顺便在这里说一下 objc中前面文章提到的数据类型
    typedef struct objc_category *Category;

    struct objc_category {
        char *category_name                          OBJC2_UNAVAILABLE; // 分类名
        char *class_name                             OBJC2_UNAVAILABLE; // 分类所    属的类名
    struct objc_method_list *instance_methods    OBJC2_UNAVAILABLE; // 实例方法列表
    struct objc_method_list *class_methods       OBJC2_UNAVAILABLE; // 类方法列表
    struct objc_protocol_list *protocols         OBJC2_UNAVAILABLE; // 分类所实现的协议列表
    }
    

    这是分类的结构体 和类是不是很相似啊?哈哈

    我们再来看看协议的struct

    typedef struct objc_object Protocol;
    

    简单明了 大家一看,这不就是对象嘛

     struct objc_object {
        Class isa  OBJC_ISA_AVAILABILITY; //实例对象拥有指向类的指针
        };
    

    class isa 指向了实现协议的类..(自然也将协议中的方法在实现的类中可以找到)

    runtime.h 提供了很多接口让开发者灵活使用,有对类的,对对象的,对属性,方法的.(分类的东西已经包含在类中),对协议的

    我们可以在程序中动态产生 继承 改变 注册 获得 销毁 各个我门想要操作

    参考 apple runtime

    最有趣又著名的莫过于sclector swizzing

    这就是非常有用的的hook....
    下一篇再讲

    相关文章

      网友评论

      本文标题:ios浅谈runtime

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