美文网首页iOS 开发 Objective-C iOS常用
iOS 底层 day16 runtime的使用 runtimeA

iOS 底层 day16 runtime的使用 runtimeA

作者: 望穿秋水小作坊 | 来源:发表于2020-09-08 18:01 被阅读0次

一、什么是 Runtime?平时项目中有用过么?

1. 什么是 Runtime?
  • Objective-C 是一门动态性比较强的编程语言,允许把很多操作都推迟到运行时决定。
  • Objective-C 的动态性就是由 Runtime 来支撑和实现的,Runtime 是一套 C 语言封装的 API,封装了很多动态性相关的函数。
2. 平时项目中有用过么(四点)?
  • 使用 objc_setAssociatedObject 关联对象技术,动态为分类添加属性
  • 使用 class_copyIvarList 遍历类的成员变量,①字典转模型 ②自动归档解档 ③寻找系统空间的隐藏属性,从而修改展示(比如拿到 textfield 的 placeholder所对应的 label)
  • 使用 method_exchangeImplementations 交换方法实现,用于替换一些系统的默认实现
  • 使用 objc_msgSend消息转发机制,可以用于解决平时方法找不到,需要动态生成方法的情况

二、RuntimeAPI 的具体使用例子

1. 使用 objc_setAssociatedObject 关联对象技术,动态为分类添加属性
2. 字典转模型简化版的实现
  • 创建 NSObject 的分类,实现如下代码
#import "NSObject+YYJsonToModel.h"
#import <objc/runtime.h>
@implementation NSObject (YYJsonToModel)
-(void)yy_modelFromJson:(NSDictionary *)dic {
    unsigned int ivarCount;
    Ivar *ivars = class_copyIvarList([self class], &ivarCount);
    for (int i = 0 ; i < ivarCount; i++) {
        NSMutableString *name = [NSMutableString stringWithUTF8String:ivar_getName(ivars[i])];
        if ([name hasPrefix:@"_"]) {
            name = [[name substringFromIndex:1] mutableCopy];
        }
        [self setValue:dic[name] forKey:name];
    }
    free(ivars);
}
@end
  • 使用如下:
int main(int argc, const char * argv[]) {
    NSDictionary *dic = @{
        @"name":@"LYY",
        @"age":@10,
        @"height":@170
    };
    YYPerson *person = [[YYPerson alloc] init];
    [person yy_modelFromJson:dic];
    NSLog(@"断点");
    return 0;
}
  • 这样我们就实现了一个简单的 JsonModel 的功能
  • 如果要达到库的标准,还需要非常多的细节考虑和增加扩展性(比如继承、类嵌套等)
3. 自动归档、自动解档(NSSecureCoding)
  • 代码实现
#import "YYPerson.h"
#import <objc/runtime.h>
@implementation YYPerson
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder {
    if (self = [super init]) {
        Class cls = self.class;
        // 截取类和父类的成员变量
        while (cls && cls != [NSObject class]) {
            unsigned int ivarCount = 0;
            Ivar *ivars = class_copyIvarList(cls, &ivarCount);
            for (int i = 0; i < ivarCount; i++) {
                NSString *key = [NSString stringWithUTF8String:ivar_getName(ivars[i])];
                id value = [coder decodeObjectForKey:key];
                if (value) { // 增加容错
                    [self setValue:value forKey:key];
                 }
            }
            // 获得c的父类
            cls = [cls superclass];
            free(ivars);
        }
    }
    return self;
}

- (void)encodeWithCoder:(nonnull NSCoder *)coder {
    Class cls = self.class;
    // 截取类和父类的成员变量
    while (cls && cls != [NSObject class]) {
        unsigned int ivarCount = 0;
        Ivar *ivars = class_copyIvarList(cls, &ivarCount);
        for (int i = 0; i < ivarCount; i++) {
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivars[i])];
            id value = [self valueForKey:key];
            if (value) { // 增加容错
                [coder encodeObject:value forKey:key];
            }
        }
        cls = [cls superclass];
        // 释放内存
        free(ivars);
    }
}
+ (BOOL)supportsSecureCoding{
    return YES;
}
@end
  • 代码调用(NSKeyedArchiver)
int main(int argc, const char * argv[]) {
    YYPerson *person = [[YYPerson alloc] init];
    person.name = @"LYY";
    person.age = 10;
    person.height = 180;
    NSLog(@"person: %@",person);
    
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:person requiringSecureCoding:YES error:nil];
    NSLog(@"data: %@",data);
    
    YYPerson *decodePerson = [NSKeyedUnarchiver unarchivedObjectOfClass:[YYPerson class] fromData:data error:nil];
    NSLog(@"decodePerson: %@",decodePerson);
    
    return 0;
}
  • iOS6中,苹果引入了一个新的协议,是基于NSCoding的,叫做NSSecureCoding。NSSecureCoding和NSCoding是一样的,除了在解码时要同时指定key和要解码的对象的类,如果要求的类和从文件中解码出的对象的类不匹配,NSCoder会抛出异常,告诉你数据已经被篡改了。大部分支持NSCoding的系统对象都已经升级到支持NSSecureCoding了

  • NSKeyedArchiver 就是用来存储对象数据到本地,即归档

  • NSKeyedUnarchiver,负责从本地存储还原对象数据,即反归档

  • 同时需要配合 NSCoding 使用,实现序列化以及反序列化

  • 存储的文件本质是一个 plist 文件

三、RuntimeAPI(常用)

1. RuntimeAPI - 类
  • 动态创建一个类(参数:父类,类名,额外的内存空间)
Class objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, 
                       size_t extraBytes) 
  • 注册一个类(要在类注册之前添加成员变量)
void objc_registerClassPair(Class _Nonnull cls)
  • 获取 isa 指向的 Class、设置 isa 指向的 Class
Class object_getClass(id _Nullable obj) 
Class object_setClass(id _Nullable obj, Class _Nonnull cls) 
  • 判断一个 OC 对象是否为 Class
BOOL object_isClass(id _Nullable obj)
  • 判断一个 OC 对象是否为元类
BOOL class_isMetaClass(id _Nullable obj)
  • 获取父类
Class class_getSuperclass(Class _Nullable cls) 
2. RuntimeAPI - 成员变量
  • 获取一个实例变量信息
Ivar class_getInstanceVariable(Class _Nullable cls, const char * _Nonnull name)
  • 拷贝实例变量列表(最后需要调用 free 释放)
Ivar *class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount)
  • 设置和获取成员变量的值
void object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value) 
id object_getIvar(id _Nullable obj, Ivar _Nonnull ivar) 
  • 动态添加成员变量(已经注册的类是不能动态添加成员变量的)
BOOL class_addIvar(Class _Nullable cls, const char * _Nonnull name, size_t size, 
              uint8_t alignment, const char * _Nullable types) 
  • 获取成员变量的相关信息
const char * ivar_getName(Ivar _Nonnull v) 
const char * ivar_getTypeEncoding(Ivar _Nonnull v) 
3. RuntimeAPI - 属性
  • 拷贝属性列表(最后需要调用 free 释放)
objc_property_t *class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount)
  • 动态添加属性
BOOL class_addProperty(Class _Nullable cls, const char * _Nonnull name,
                  const objc_property_attribute_t * _Nullable attributes,
                  unsigned int attributeCount)
4. RuntimeAPI - 方法
  • 获得一个实例方法、类方法
Ivar class_getInstanceVariable(Class _Nullable cls, const char * _Nonnull name)
Ivar class_getClassVariable(Class _Nullable cls, const char * _Nonnull name)
  • 方法实现相关操作
IMP class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name) 
IMP method_setImplementation(Method _Nonnull m, IMP _Nonnull imp) 
void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) 
  • 动态添加方法
BOOL class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                const char * _Nullable types)
  • 动态替换方法
IMP class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                    const char * _Nullable types) 
  • 获取方法的相关信息(带有 copy 的需要调用 free 释放)
SEL method_getName(Method _Nonnull m)
IMP method_getImplementation(Method _Nonnull m)
const char * method_getTypeEncoding(Method _Nonnull m)
  • 选择器相关
const char * _Nonnull sel_getName(SEL _Nonnull sel)
SEL sel_registerName(const char * _Nonnull str)
  • 用 block 作为方法实现
IMP imp_implementationWithBlock(id _Nonnull block)
id imp_getBlock(IMP _Nonnull anImp)
BOOL imp_removeBlock(IMP _Nonnull anImp)

四、RuntimeAPI 的一些练习

1. 动态创建一个 YYCat 类,为它添加 nameweight 两个成员变量。再添加一个 print 方法,然后创建实例,分别为 nameweight 赋值再将它们打印出来,调用方法 print
int main(int argc, const char * argv[]) {
    Class YYCat = objc_allocateClassPair([NSObject class], "YYCat", 0);
    class_addIvar(YYCat, "_name", 8, 1, @encode(NSString*));
    class_addIvar(YYCat, "_age", 4, 1, @encode(int));
    IMP catRunImp = imp_implementationWithBlock(^{
        printf("cat run run: %s\n", __func__);
    });
    class_addMethod(YYCat, @selector(run), catRunImp, "v");
    objc_registerClassPair(YYCat);
    
    id cat = class_createInstance(YYCat, 0);
    [cat setValue:[NSString stringWithFormat:@"Mimi"] forKey:@"_name"];
    [cat setValue:@3 forKey:@"_age"];
    [cat run];
    NSLog(@"cat name is %@, age is %d", [cat valueForKey:@"_name"], [[cat valueForKey:@"_age"] intValue]);
    NSLog(@"断点观察");
    return 0;
}
2. 将 YYPersonrun 的方法实现 和 YYDogeat 的方法实现 进行交换
int main(int argc, const char * argv[]) {
    YYPerson *person = [[YYPerson alloc] init];
    YYDog *dog = [[YYDog alloc] init];
    Method personRun = class_getInstanceMethod([YYPerson class], @selector(run));
    Method dogEat = class_getInstanceMethod([YYDog class], @selector(eat));
    method_exchangeImplementations(personRun, dogEat);
    [person run];
    [dog eat];
    return 0;
}
  • class_getInstanceMethod 获取实例方法
  • class_getClassMethod 获取类方法

四、源码和本质追踪的时候的一些思路

1. 我们在研究OC 底层实现的时候,一般有三种思路
  • ① 转成 C++ 代码(有一点误差 ,99%正确)
  • ② 查看汇编(汇编可以断点查看,也可以用 Xocde 的 assembly 功能 )
  • ③ 查看源码
2. 遗留问题:思考用 object_setIvar 会触发 KVO 吗?

相关文章

网友评论

    本文标题:iOS 底层 day16 runtime的使用 runtimeA

    本文链接:https://www.haomeiwen.com/subject/amndektx.html