iOS Runtime(一):简介

Runtime简介

Runtime 是 C,C++ 汇编一起写成的 API,有两个版本 Modern 和 Legacy,OC 2.0 之后用的是 Modern Version 版本,可以运行在 iOS2.0 和 macOS 10.5 之后的系统中。

Runtime 是一个用C、C++、汇编编写的运行时库,包含了很多 C 语言的 API,封装了很多动态性相关的函数。

Objective-C 也因为Runtime的存在成为一门动态运行时语言,允许很多操作推迟到程序运行时再进行。OC的动态性就是由Runtime来支撑和实现的,Rumtime就是它的核心,我们平时编写的OC代码,底层都是转换成了Runtime API进行调用。

都说 OC 是一门动态运行时语言,那什么是运行时?什么是编译时?

  • 运行时:代码跑起来之后会装载到内存中,提供运行时功能
  • 编译时:正在编译的时间,就是把源代码(高级语言)翻译成机器能识别的语言->机器语言->二进制

Runtime有哪些应用

Runtime 可以做的事情有很多,如

  • 动态交换方法
  • KVO 实现
  • 关联对象:给分类添加属性
  • 消息转发:项目中一些防崩溃处理
  • 遍历类的所有成员变量(字典转模型、自动归档解档)

关联对象(Objective-C Associated Objects)给分类增加属性

我们都是知道分类是不能自定义属性和变量的。下面通过关联对象实现给分类添加属性。

关联对象 Runtime 提供了下面几个接口:

objc 复制代码
//设置关联对象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
//获取关联的对象
id objc_getAssociatedObject(id object, const void *key)
//移除关联的对象
void objc_removeAssociatedObjects(id object)

参数解释

objc 复制代码
id object:被关联的对象
const void *key:关联的key,要求唯一
id value:关联的对象
objc_AssociationPolicy policy:内存管理的策略

内存管理的策略

objc 复制代码
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

举个例子

objc 复制代码
/*! runtime set */
#define C4Kit_Objc_setObj(key, value) objc_setAssociatedObject(self, key, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
/*! runtime get */
#define C4Kit_Objc_getObj objc_getAssociatedObject(self, _cmd)

@interface WKWebView (C4Kit)

@property (nonatomic, assign) CGFloat webViewHeigt;
/// 判断是否添加了观察者
@property (nonatomic, assign) BOOL hadAddObserver;

@end

  
@implementation WKWebView (C4Kit)

- (void)setWebViewHeigt:(CGFloat)webViewHeigt
{
    C4Kit_Objc_setObj(@selector(webViewHeigt), @(webViewHeigt));
}

- (CGFloat)webViewHeigt
{
    return [C4Kit_Objc_getObj floatValue];
}

方法添加

实际上添加方法刚才在讲消息转发的时候,动态方法解析的时候就提到了。

objc 复制代码
//class_addMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name, IMP  _Nonnull imp, const char * _Nullable types)
class_addMethod([self class], sel, (IMP)fooMethod, "v@:");
  • cls 被添加方法的类
  • name 添加的方法的名称的SEL
  • imp 方法的实现。该函数必须至少要有两个参数,self,_cmd
  • 类型编码

如动态方法解析时,动态添加的实现SEL

objc 复制代码
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(run)) {
        SEL readBookSel = @selector(readBook);
        Method readM = class_getInstanceMethod(self, readBookSel);
        IMP readImp = class_getMethodImplementation(self, readBookSel);
        const char *type = method_getTypeEncoding(readM);
        return class_addMethod(self, sel, readImp, type);
    }
    return [super resolveInstanceMethod:sel];
}

方法替换

下面实现一个替换ViewController的viewDidLoad方法的例子。

objc 复制代码
@implementation ViewController

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        SEL originalSelector = @selector(viewDidLoad);
        SEL swizzledSelector = @selector(jkviewDidLoad);
        
        Method originalMethod = class_getInstanceMethod(class,originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class,swizzledSelector);
        
        //judge the method named  swizzledMethod is already existed.
        BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        // if swizzledMethod is already existed.
        if (didAddMethod) {
            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        }
        else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)jkviewDidLoad {
    NSLog(@"替换的方法");
    // 此处调用的是系统方法 viewDidLoad
    [self jkviewDidLoad];
}

- (void)viewDidLoad {
    NSLog(@"自带的方法");
    
    [super viewDidLoad];
}

@end

需要注意的是,jkviewDidLoad的实现里调用[self jkviewDidLoad];实际调用的是系统方法viewDidLoad。

KVO

KVO 的底层实现大致可以描述如下:

  1. 动态创建子类 NSKVONotifying_XXX,
  2. 创建 class 方法,class 指向原类,为了隐藏这个子类
  3. 创建 setter 方法,内部把消息转发给父类,并触发回调 observeValueForKeyPath
  4. 创建 dealloc 方法
  5. 创建 isKVOA
  6. 处理 removeObserver 操作
    1. isa 指回父类
    2. 不会移除这个子类,即把这个子类缓存下来,为防止下次监听时再次创建耗费性能,这里用空间换时间

详细请看iOS KVO详解

实现NSCoding的自动归档和自动解档

原理描述:用runtime提供的函数遍历Model自身所有属性,并对属性进行encode和decode操作。 核心方法:在Model的基类中重写方法:

objc 复制代码
- (id)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super init]) {
        unsigned int outCount;
        Ivar * ivars = class_copyIvarList([self class], &outCount);
        for (int i = 0; i < outCount; i ++) {
            Ivar ivar = ivars[i];
            NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            [self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
        }
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    unsigned int outCount;
    Ivar * ivars = class_copyIvarList([self class], &outCount);
    for (int i = 0; i < outCount; i ++) {
        Ivar ivar = ivars[i];
        NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
        [aCoder encodeObject:[self valueForKey:key] forKey:key];
    }
}

实现字典和模型的自动转换(MJExtension)

原理描述:用runtime提供的函数遍历Model自身所有属性,如果属性在json中有对应的值,则将其赋值。 核心方法:在NSObject的分类中添加方法

objc 复制代码
- (instancetype)initWithDict:(NSDictionary *)dict {

    if (self = [self init]) {
        //(1)获取类的属性及属性对应的类型
        NSMutableArray * keys = [NSMutableArray array];
        NSMutableArray * attributes = [NSMutableArray array];
        /*
         * 例子
         * name = value3 attribute = T@"NSString",C,N,V_value3
         * name = value4 attribute = T^i,N,V_value4
         */
        unsigned int outCount;
        objc_property_t * properties = class_copyPropertyList([self class], &outCount);
        for (int i = 0; i < outCount; i ++) {
            objc_property_t property = properties[i];
            //通过property_getName函数获得属性的名字
            NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
            [keys addObject:propertyName];
            //通过property_getAttributes函数可以获得属性的名字和@encode编码
            NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
            [attributes addObject:propertyAttribute];
        }
        //立即释放properties指向的内存
        free(properties);

        //(2)根据类型给属性赋值
        for (NSString * key in keys) {
            if ([dict valueForKey:key] == nil) continue;
            [self setValue:[dict valueForKey:key] forKey:key];
        }
    }
    return self;

}

参考链接

Runtime 源码
Runtime 可编译源码
苹果官方文档 Runtime
iOS Runtime详解

相关推荐
黑科技iOS上架3 小时前
双引擎解决oc/swift开发引用加固难题
开发语言·ios·swift·审核·ios混淆·执行程序差异化
wechatbot8884 小时前
企微第三方自动化开发:原生能力无阉割 API 开放平台介绍
后端·ios·微信·企业微信·ai编程·ipad
黑科技iOS上架4 小时前
iOS framework/dylib动态库定向加固实战
macos·ios·objective-c·cocoa·审核·ios混淆·执行程序差异化
可乐鸡翅yeah_1 天前
fmp4 碎片 MP4 格式 HLS 流,和传统 TS 分片有什么不一样
开发语言·javascript·ios·音视频·safari·m3u8·m3u8在线播放
茶底世界之下1 天前
为什么高精度透明图片上传到 Metal 时,必须把“拷贝像素”与“混合合成”分开
ios
老李IT笔记1 天前
iOS 27 更新延迟只能给 1 到 90 天:两个参数怎么配,配错了会发生什么
ios·智能手机
Helix2501 天前
移动端浏览器内核之争:iOS 为何强制使用 WebKit?
ios·safari·webkit·wkwebview·浏览器内核·blink·ios浏览器内核
传奇开心果编程2 天前
【SwiftUI提高练中学】第13课 实时活动与灵动岛:ActivityKit 实战
学习·ui·ios·swiftui·swift
可乐鸡翅yeah_2 天前
M3U8 防盗链 URL‑Token 签名,新手开发实操避坑
开发语言·javascript·ios·音视频·safari