iOS Runtime(一):简介

2 阅读5分钟

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 提供了下面几个接口:

//设置关联对象
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)

参数解释

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

内存管理的策略

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. */
};

举个例子

/*! 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];
}

方法添加

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

//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

+ (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];
}

方法替换

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

@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自身所有属性,并对属性进行encodedecode操作。 核心方法:在Model的基类中重写方法:

- (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的分类中添加方法

- (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详解