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

原文地址:https://www.cnblogs.com/yangwenhuan/p/8485806.html