0

Objective-C Runtime

 3 years ago
source link: http://www.cnblogs.com/ebamboo/p/14285440.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

一、Objective-C Runtime 简介

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

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

每个对象都有个 isa 属性指向对象所属类;有个 super_class 属性指向所属类的父类;

类也是对象,它的 isa 指向类的元类;类的元类的父类等于类的父类的元类。

实例的属性存储在实例中,实例的方法存储在所属类中,类方法存储在元类中。

类中存放着实例方法列表,在这个方法列表中 SEL 作为 key,IMP 作为 value。

mmYZ3iZ.png!mobile

三、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;
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK