zoukankan      html  css  js  c++  java
  • Objective-C Runtime

    一、引言

      Objective-C总是尽可能把事情从编译时期和链接时期,推迟到运行时期(Runtime)来动态执行。这就意味着Objective-C不仅需要编译器,还需要一套runtime system来执行编译后的代码。

    二、与Runtime交互 

      苹果提供了三种方式与Runtime进行交互:

      1、Objective-C源码(隐式交互) 

        在大多数情况下,只需要写好并编译Objective-C源码,runtime system就会自动在幕后默默为我们工作。

      2、NSObject中的方法

      3、直接调用Runtime函数 

    三、消息机制

      引言中为什么说是动态执行呢?这需要深入理解Objective-C的消息(Messaging)机制。

      [receiver message],表示给接收者发送一条消息。

    clang -rewrite-objc demo.m

      利用clang重写它,得到如下C代码:

    ((void (*)(id, SEL, NSString *, NSInteger))(void *)objc_msgSend)((id)receiver, sel_registerName("message:arg2:"), (NSString *)arg1, (NSInteger)arg2);

      可以看到,这个表达式会被编译器转成函数调用:

          1)objc_msgSend(receiver, selector)

          如果消息中包含参数:

      2)objc_msgSend(receiver, selector, arg1, arg2, ...)

          在编译时期,只确定要向receiver发送message(即知道selector及相关参数),但是receiver如何响应这条message,就要看运行时期动态绑定到的selector对应的方法实现了。   

      1、动态绑定

      在理解动态绑定之前,先要来看看底层一些重要结构体。

    // id类型其实是对象结构体指针
    typedef struct objc_object *id;
    
    // 对象结构体
    struct objc_object {
        Class isa; // 指向对象的类。通过isa指针,对象可以访问它的类
    };
    

      

    // Class类型其实是类结构体指针
    typedef struct objc_class *Class;
    

      

    // 类结构体
    struct objc_class {
        Class isa; // 指向类的元类,Objective-C中,类也是一个对象
        Class super_class; // 指向父类
        const char *name; // 类名
        struct objc_method_list **methodLists; // 方法链表,表项是struct objc_method *
        struct objc_cache *cache; // 缓存调用过的方法,加速方法查找
        struct objc_protocol_list *protocols; // 协议列表
    };
    typedef struct objc_selector *SEL;
    // 方法结构体
    struct objc_method {
        SEL method_name; // selector其实就是方法实现的唯一标识
        char *method_types; // 入参类型和返回值类型
        IMP method_imp; // 方法实现
    };
    

      

    // IMP(implementation),其实就是一个函数指针,指向方法实现的地址
    typedef id (*IMP)(id, SEL, ...); 
    

      

      当一条消息发送给一个对象时,objc_msgSend函数会通过对象的isa指针去对象的类结构体的方法列表里查找IMP;如果找不到,就顺着类的super_class去父类的方法列表里查找,以此类推,直到NSObject类,还找不到就会返回_objc_msgForward(默认IMP),由此进入消息转发流程。一旦找到,objc_msgSend函数就把receiver结构体、selector以及方法参数列表传给方法的具体实现。为了加快查找速度,runtime会缓存调用过的方法。示意图如下: 

      2、使用隐含参数

     

      3、获取方法地址

      规避动态绑定的唯一套路是拿到方法的地址,然后当成函数那样调用。但是这种做法的适用场景很少,除非一个方法在短时间内被大量调用,为了减少消息发送的开销才这么干。

      4、动态方法解析

    @dynamic propertyName;
    instrumentObjcMessageSends(YES);  

      5、消息转发

      当查找不到selector时,runtime会给receiver对象发送一条forwardInvocation消息,即进入消息转发流程。forwardInvocation消息的参数是一个NSInvocation对象,该对象封装了动态绑定时的原始消息及其参数。我们可以实现forwardInvocation方法来响应原始消息。forwardInvocation,顾名思义,即“转发调用”,通常用于转发原始消息给其他对象。示意图如下:

    resolveInstanceMethod:Dynamically provides an implementation for a given selector for an instance method.

    forwardingTargetForSelector:Returns the object to which unrecognized messages should first be directed. 

    methodSignatureForSelector:Returns an NSMethodSignature object that contains a description of the method identified by a given selector.

    doesNotRecognizeSelector:Handles messages the receiver doesn’t recognize.

    unrecognized selector sent to instance xxxxxx

     向nil对象发消息

    objc_msgSendSuper

    obj_msgSend的实现是用汇编写的

    声明的属性

      当编译器遇到属性声明,  

    四、Method Swizzling(方法搅拌)

      Method Swizzling,利用category来实现在运行时动态改变selector对应的IMP。这有什么用呢?拦截、解耦。

      

    #import <objc/runtime.h>
    
    @implementation UIViewController (Tracking)
    
    + (void)load {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Class class = [self class];
    
            SEL originalSelector = @selector(viewWillAppear:);
            SEL swizzledSelector = @selector(xxx_viewWillAppear:);
    
            Method originalMethod = class_getInstanceMethod(class, originalSelector);
            Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    
            // When swizzling a class method, use the following:
            // Class class = object_getClass((id)self);
            // ...
            // Method originalMethod = class_getClassMethod(class, originalSelector);
            // Method swizzledMethod = class_getClassMethod(class, swizzledSelector);
    
            BOOL didAddMethod =
                class_addMethod(class,
                    originalSelector,
                    method_getImplementation(swizzledMethod),
                    method_getTypeEncoding(swizzledMethod));
    
            if (didAddMethod) {
                class_replaceMethod(class,
                    swizzledSelector,
                    method_getImplementation(originalMethod),
                    method_getTypeEncoding(originalMethod));
            } else {
                method_exchangeImplementations(originalMethod, swizzledMethod);
            }
        });
    }
    
    #pragma mark - Method Swizzling
    
    - (void)xxx_viewWillAppear:(BOOL)animated {
        [self xxx_viewWillAppear:animated];
        NSLog(@"viewWillAppear: %@", self);
    }
    
    @end
    

      

    // 参考链接:http://limboy.me/tech/2018/03/04/ios-lightweight-hotfix.html?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io

    参考链接:

      1)Runtime官方文档: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html

      2)NSObject官方文档:https://developer.apple.com/documentation/objectivec/nsobject?language=objc

  • 相关阅读:
    node.js---sails项目开发(3)
    node.js---sails项目开发(2)
    基于 phantomjs 的自动化测试---(1)
    node.js---sails项目开发(1)
    mongoose基于mongodb的数据评论设计
    js复杂数据格式提交
    [LeetCode]Rotate Image
    [LeetCode]Minimum Path Sum
    [LeetCode]Generate Parentheses
    [LeetCode]Gray Code
  • 原文地址:https://www.cnblogs.com/yangwenhuan/p/8485806.html
Copyright © 2011-2022 走看看