深入浅出 Runtime

缘起

我不大喜欢 runtime,尤其是在我刚刚接触 iOS 开发的时候,一个原因是刚开始的时候觉得,这个调用方法名怎么是用下划线的,那时候觉得看的非常别扭。而后慢慢熟悉了 iOS 开发之后,尤其在了解 OC 的 GCD 也是下划线命名的方法之后,也不觉得那么面目可憎了。

不过相对于 GCD,runtime 在平时开发中,真的用得少,更后来到铃盛做开发,公司是用 Swift 开发的应用,用到 runtime 的时候就更少了。于是乎,平时对运行时这部分就用的比较少。也没有抽时间去专门了解一下。

用得少并不代表不知道,尤其是用 OC 的时候接触过 JSPatch,在了解了一下原理后,发现 runtime 在处理一些特殊领域的问题的时候尤其有用。如果你平时有 听过 JSPatch、FLEX、Swizzle这些名词,甚至用过这些应用或者是技术的话,那么你或多或少应该接触过 runtime。

Runtime 能做什么

Runtime 能做什么呢? 一个常见应用是 Method Swizzling,通俗的说是做方法实现的替换。它能解决的问题包括但不限于:

  • 修复方法旧有实现的一些 bug
  • 重构代码,比如给 VC 做打点
  • 重新实现方法,然某个方法适配旧的系统

对于这个功能我其实没有很喜欢,因为它改变了某个方法的实现,如果出问题的时候还是比较迷茫的,而且要是滥用的话,还是蛮危险的,代码的质量得不到保证,所以之前在开发的时候还蛮少使用的。

另外一个应用是关联对象,也就是动态添加一个属性,比如给 cell 添加高度缓存属性之类的。

这两个应用都有 Swift 的版本。

不废话,我们上代码:
方法替换
Swift 版本

extension UIViewController {
    
    private static let swizzleMethod: Void = {
        let originalViewDidLoadSelector = #selector(viewDidLoad)
        let swizzledViewDidLoadSelector = #selector(swizzled_viewDidLoad)
        swizzlingForClass(UIViewController.self, originalSelector: originalViewDidLoadSelector, swizzledSelector: swizzledViewDidLoadSelector)
    }()
    
    @objc func swizzled_viewDidLoad() {
        swizzled_viewDidLoad()
    }
    
    private static func swizzlingForClass(_ forClass: AnyClass, originalSelector: Selector, swizzledSelector: Selector) {
        let originalMethod = class_getInstanceMethod(forClass, originalSelector)
        let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector)
        guard let oMethod = originalMethod, let sMethod = swizzledMethod else {
            return
        }
        if class_addMethod(forClass, originalSelector, method_getImplementation(sMethod), method_getTypeEncoding(sMethod)) {
            class_replaceMethod(forClass, swizzledSelector, method_getImplementation(oMethod), method_getTypeEncoding(oMethod))
        } else {
            method_exchangeImplementations(oMethod, sMethod)
        }
    }
}

OC 版本

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class selfClass = object_getClass([self class]);
        SEL originalSEL = @selector(viewDidLoad);
        Method oriMethod = class_getInstanceMethod(selfClass, originalSEL);
        
        SEL newSEL = @selector(swizzleViewDidLoad);
        Method newMethod = class_getInstanceMethod(selfClass, newSEL);
        
        if(class_addMethod(selfClass, originalSEL, newSEL, method_getTypeEncoding(newSEL))) {
            class_replaceMethod(selfClass, newSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
        }else{
            method_exchangeImplementations(oriMethod, newMethod);
        }
    });
}

关联对象:
Swift 版本

extension UIViewController {
    private struct AssociateKeys {
        static var storyboardName = "UIViewController_StoryboardName"
    }
    
    var storyboardName:String? {
        get {
            return objc_getAssociatedObject(self, &AssociateKeys.storyboardName) as? String
        }
        
        set{
            if let newValue = newValue {
                objc_setAssociatedObject(self, &AssociateKeys.storyboardName, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            }
        }
    }
}

OC 版本

//.h
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface UIViewController (UIViewController_Ext)
@property(nonatomic,copy) NSString *storyboardName;
@end

NS_ASSUME_NONNULL_END

//.m
#import <objc/runtime.h>
#import "UIViewController+UIViewController_Ext.h"

@implementation UIViewController (UIViewController_Ext)
@dynamic storyboardName;
//dynamic 声明是告诉编译器,这个属性是在运行时通过 get set 得到的

- (NSString *)storyboardName {
    return objc_getAssociatedObject(self, @selector(storyboardName));
}

- (void)setStoryboardName:(NSString *)storyboardName {
    objc_setAssociatedObject(self, @selector(storyboardName), storyboardName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end

基本实现到这个程度就好了。如果你想了解更多细节,可以继续往下看。

Runtime 是什么

类结构

isa 指针

可以分为指针型 isa(isa 的值代表 Class 的地址)和非指针型 isa(值的部分代表 Class 的地址),
对于对象,指向类对象

objc_object

也就是 id

objc_class

也就是 Class,继承自objc_object,实际上也是一个类。objc_class 在整个 OC 类结构里处于中心。如图:

cache_t

用来快速查找方法执行函数,是一种哈希表

class_data_bits_t

是对class_rw_t的封装,存储了相关类的读写信息。class_rw_t是可以由开发改写的,而 class_ro_t 不能改写,类创建的时候就固定下来了。不过如果是动态添加的类,那么就绕过了编译阶段,这时候可以动态添加变量。

函数结构

对于 types,用到了一种叫 Type Encoding 的技术,它将 v 对应 void,@ 对应着 id 类型,:对应着 SEL。
举个例子,一个无返回值,无参数值的方法,可以标识为 v@:

消息传递

OC 中所有方法都会转为objc_msgSend的样式,即:
objc_msgSend(self, SEL)

Comments
Write a Comment