当前位置 博文首页 > Objective-C Runtime

    Objective-C Runtime

    作者:ebamboo 时间:2021-01-16 14:06

    一、Objective-C Runtime 简介

    Objective-C Runtime 是一个运行时库。它可以在程序运行时改变程序的结构如:添加属性、添加方法、交换方法等。

    二、对象、类的结构和关系

    每个对象都有个 isa 属性指向对象所属类;有个 super_class 属性指向所属类的父类;
    类也是对象,它的 isa 指向类的元类;类的元类的父类等于类的父类的元类。
    实例的属性存储在实例中,实例的方法存储在所属类中,类方法存储在元类中。
    类中存放着实例方法列表,在这个方法列表中 SEL 作为 key,IMP 作为 value。

    三、OC 方法调用过程

    1.检测 SEL 是否应该被忽略。
    2.检测发送的 target 是否为 nil ,如果是则忽略该消息。
    3.当调用实例方法时,通过 isa 指针找到实例对应的 class 并且在其中的缓存方法列表以及方法列表中进行查询,如果找不到则根据 super_class 指针在父类中查询,直至根类(NSObject 或 NSProxy)。
    当调用类方法时,通过 isa 指针找到实例对应的 metaclass 并且在其中的缓存方法列表以及方法列表中进行查询,如果找不到则根据 super_class 指针在父类中查询,直至根类(NSObject 或 NSProxy)。

    四、runtime 使用场景

    1、给分类添加“属性”

    // 在分类的 .h 文件中声明“属性”

    @property (nonatomic) NSInteger age;
    

    // 在分类的 .m 实现以下两个方法

    - (void)setAge:(NSInteger)age{
        // 使用运行时关联对象,Person对象self强引用NSNumber对象@(age),并且设置标记为"age"(可以根据该标记来获取引用的对象age,标记可以为任意字符,只要setter和getter中的标记一致就可以)
        // 参数1:源对象
        // 参数2:关联时用来标记属性的key(因为可能要添加很多属性)
        // 参数3:关联的对象
        // 参数4:关联策略。assign,retain,copy对应的枚举值
        objc_setAssociatedObject(self, "age", @(age), OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    
    - (NSInteger)age{
        // 根据"age"标识取Person对象self强引用的NSNumber对象@(age)
        // 参数1:源对象
        // 参数2:关联时用来标记属性的key(因为可能要添加很多属性)
        return [objc_getAssociatedObject(self, "age") integerValue];
    }
    
    2、动态交换两个方法的实现
    + (void)load {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Method originMethod = class_getInstanceMethod([self class], @selector(originFunction));
            Method customMethod = class_getInstanceMethod([self class], @selector(customFunction));
            // 尝试自定义方法实现添加到系统方法中
            BOOL addSuccess = class_addMethod([self class], @selector(originFunction), method_getImplementation(customMethod), method_getTypeEncoding(customMethod));
            if (addSuccess) {
                // 添加成功后,系统方法实现设置为自定义方法中
                class_replaceMethod([self class], @selector(customFunction), method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
            } else {
                method_exchangeImplementations(originMethod, customMethod);
            }
        });
    }
    
    3、动态添加方法如:自定义定时器的 target,动态添加与控制器相同的方法。
    Method targetMethod = class_getInstanceMethod([aTarget class], aSelector);
    if (!class_addMethod([BBTimerManager class], aSelector, method_getImplementation(targetMethod), method_getTypeEncoding(targetMethod))) {
        class_replaceMethod([BBTimerManager class], aSelector, method_getImplementation(targetMethod), method_getTypeEncoding(targetMethod));
    }
    
    4、动态获取属性,可以用在 NSCoding 归档中,不用一个一个设置属性的解归档。
    - (void)encodeWithCoder:(NSCoder *)encoder {
        
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList([Movie class], &count);
    
        for (int i = 0; i < count; i++) {
            // 取出 i 位置对应的成员变量
            Ivar ivar = ivars[I];
            // 查看成员变量
            const char *name = ivar_getName(ivar);
            NSString *key = [NSString stringWithUTF8String:name];
            id value = [self valueForKey:key];
            // 归档
            [encoder encodeObject:value forKey:key];
        }
        free(ivars);
    }
    
    - (id)initWithCoder:(NSCoder *)decoder {
        if (self = [super init]) {
            
            unsigned int count = 0;
            Ivar *ivars = class_copyIvarList([Movie class], &count);
            
            for (int i = 0; i < count; i++) {
                // 取出i位置对应的成员变量
                Ivar ivar = ivars[I];
                // 查看成员变量
                const char *name = ivar_getName(ivar);
                NSString *key = [NSString stringWithUTF8String:name];
                id value = [decoder decodeObjectForKey:key];
                // 设置到成员变量身上
                [self setValue:value forKey:key];
            }
            free(ivars);
        }
        return self;
    }