深入浅出 Runtime

栏目: IOS · 发布时间: 7年前

内容简介:我不大喜欢 runtime,尤其是在我刚刚接触 iOS 开发的时候,一个原因是刚开始的时候觉得,这个调用方法名怎么是用下划线的,那时候觉得看的非常别扭。而后慢慢熟悉了 iOS 开发之后,尤其在了解 OC 的 GCD 也是下划线命名的方法之后,也不觉得那么面目可憎了。不过相对于 GCD,runtime 在平时开发中,真的用得少,更后来到铃盛做开发,公司是用 Swift 开发的应用,用到 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 类结构里处于中心。如图:

深入浅出 Runtime

cache_t

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

class_data_bits_t

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

函数结构

深入浅出 Runtime

对于 types,用到了一种叫 Type Encoding 的技术,它将 v 对应 void,@ 对应着 id 类型,:对应着 SEL。

举个例子,一个无返回值,无参数值的方法,可以标识为 v@:

消息传递

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

objc_msgSend(self, SEL)

深入浅出 Runtime


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

技术元素

技术元素

[美] 凯文·凯利 / 张行舟、余倩、周峰、管策、金鑫、曾丹阳、李远、袁璐 / 译言·东西文库/电子工业出版社 / 2012-5 / 55.00元

我会将我不成熟的想法、笔记、内心争论、草稿以及对其他文章的回应都写在《技术元素》中,这样我就能知道自己到底在想些什么。——KK “技术元素”(technium)是凯文•凯利专门创造出来的词语。“技术元素不仅仅包括一些具象的技术(例如汽车、雷达和计算机等),它还包括文化、 法律、社会机构和所有的智能创造物。”简而言之,技术元素就是从人的意识中涌现出来的一切。KK把这种科技的延伸面看成一个能产生......一起来看看 《技术元素》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

MD5 加密
MD5 加密

MD5 加密工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具