iOS 底层系列 - runtime 的应用

772 阅读8分钟

runtime 方法交换

#import <objc/runtime.h>
@implementation UIViewController (Tracking)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class aClass = [self class];

        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(xxx_viewWillAppear:);

        Method originalMethod = class_getInstanceMethod(aClass, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);

        // When swizzling a class method, use the following:
        // Class aClass = object_getClass((id)self);
        // ...
        // Method originalMethod = class_getClassMethod(aClass, originalSelector);
        // Method swizzledMethod = class_getClassMethod(aClass, swizzledSelector);
        // 往类中添加方法
        BOOL didAddMethod =
        class_addMethod(aClass,
                        originalSelector,
                        method_getImplementation(swizzledMethod),
                        method_getTypeEncoding(swizzledMethod));
        //如果添加成功,就带类中不存在要替换的方法
        if (didAddMethod) {
            class_replaceMethod(aClass,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            //反之,类中已经有了想要替换的方法时
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
    [self xxx_viewWillAppear:animated];
    NSLog(@"viewWillAppear: %@", self);
}
@end

1. 动态添加Ivar

优点:

  • 动态添加Ivar我们能够通过遍历Ivar得到我们所添加的属性。

缺点:

  • 不能在已存在的class中添加Ivar,所以说必须通过objc_allocateClassPair动态创建一个class,才能调用class_addIvar创建Ivar,最后通过objc_registerClassPair注册class。
//在目标target上添加属性(已经存在的类不支持,可跳进去看注释),属性名propertyname,值value
- (void)addIvarWithtarget:(id)target withPropertyName:(NSString *)propertyName withValue:(id)value {
    if (class_addIvar([target class], [propertyName UTF8String], sizeof(id), log2(sizeof(id)), "@"))
    {
        NSLog(@"创建属性Ivar成功");
    }
}
//获取目标target的指定属性值
- (id)getIvarValueWithTarget:(id)target withPropertyName:(NSString *)propertyName
{    Ivar ivar = class_getInstanceVariable([target class], [propertyName UTF8String]);
    if (ivar) {
        id value = object_getIvar(target, ivar);
        return value;
    } else
    {
        return nil;
    }
}

2. 动态添加property

主要用到class_addProperty,class_addMethod,class_replaceProperty,class_getInstanceVariable

//在目标target上添加属性,属性名propertyname,值value
+ (void)addPropertyWithtarget:(id)target withPropertyName:(NSString *)propertyName withValue:(id)value {
    //先判断有没有这个属性,没有就添加,有就直接赋值
    Ivar ivar = class_getInstanceVariable([target class], [[NSString stringWithFormat:@"_%@", propertyName] UTF8String]);
    if (ivar)
    {
        return;
    }

    /*
     objc_property_attribute_t type = { "T""@/"NSString/"" };
     objc_property_attribute_t ownership = { "C""" }; // C = copy
     objc_property_attribute_t backingivar  = { "V""_privateName" };
     objc_property_attribute_t attrs[] = { type, ownership, backingivar };
     class_addProperty([SomeClass class], "name", attrs, 3);
     */

    //objc_property_attribute_t所代表的意思可以调用getPropertyNameList打印,大概就能猜出。
    objc_property_attribute_t type = { "T", [[NSString stringWithFormat:@"@/%@/",NSStringFromClass([value class])] UTF8String] };
    objc_property_attribute_t ownership = { "&""N" };
    objc_property_attribute_t backingivar  = { "V", [[NSString stringWithFormat:@"_%@", propertyName] UTF8String] };
    objc_property_attribute_t attrs[] = { type, ownership, backingivar };
    if (class_addProperty([target class][propertyName UTF8String], attrs, 3))
    {
        //添加get和set方法
        class_addMethod([target class], NSSelectorFromString(propertyName), (IMP)getter, "@@:");
        class_addMethod([target class], NSSelectorFromString([NSString stringWithFormat:@"set%@:",[propertyName capitalizedString]]), (IMP)setter, "v@:@");
        //赋值
        [target setValue:value forKey:propertyName];
        NSLog(@"%@", [target valueForKey:propertyName]);
        NSLog(@"创建属性Property成功");
    }
    else {
        class_replaceProperty([target class][propertyName UTF8String], attrs, 3);
        //添加get和set方法
        class_addMethod([target class], NSSelectorFromString(propertyName), (IMP)getter, "@@:");
        class_addMethod([target class], NSSelectorFromString([NSString stringWithFormat:@"set%@:",[propertyName capitalizedString]]), (IMP)setter, "v@:@");
        //赋值
        [target setValue:value forKey:propertyName];
    }
}

id getter(id self1, SEL _cmd1)
{
    NSString *key = NSStringFromSelector(_cmd1);
    Ivar ivar = class_getInstanceVariable([self1 class], "_dictCustomerProperty");
    //basicsViewController里面有个_dictCustomerProperty属性
    NSMutableDictionary *dictCustomerProperty = object_getIvar(self1, ivar);
    return [dictCustomerProperty objectForKey:key];
}

void setter(id self1, SEL _cmd1, id newValue)
{
    //移除set
    NSString *key = [NSStringFromSelector(_cmd1) stringByReplacingCharactersInRange:NSMakeRange(03) withString:@""];
    //首字母小写
    NSString *head = [key substringWithRange:NSMakeRange(01)];
    head = [head lowercaseString];
    key = [key stringByReplacingCharactersInRange:NSMakeRange(01) withString:head];
    //移除后缀 ":"
    key = [key stringByReplacingCharactersInRange:NSMakeRange(key.length - 11) withString:@""];
    Ivar ivar = class_getInstanceVariable([self1 class], "_dictCustomerProperty");
    //basicsViewController里面有个_dictCustomerProperty属性
    NSMutableDictionary *dictCustomerProperty = object_getIvar(self1, ivar);
    if (!dictCustomerProperty)
    {
        dictCustomerProperty = [NSMutableDictionary dictionary];
        object_setIvar(self1, ivar, dictCustomerProperty);
    }
    [dictCustomerProperty setObject:newValue forKey:key];
}

+ (id)getPropertyValueWithTarget:(id)target withPropertyName:(NSString *)propertyName
{
    //先判断有没有这个属性,没有就添加,有就直接赋值
    Ivar ivar = class_getInstanceVariable([target class], [[NSString stringWithFormat:@"_%@", propertyName] UTF8String]);
    if (ivar)
    {
        return object_getIvar(target, ivar);
    }
    ivar = class_getInstanceVariable([target class], "_dictCustomerProperty");
    //basicsViewController里面有个_dictCustomerProperty属性
    NSMutableDictionary *dict = object_getIvar(target, ivar);
    if (dict && [dict objectForKey:propertyName]) {
        return [dict objectForKey:propertyName];
    }
    else
    {
        return nil;
    }
}

3. 动态添加方法

void dynamicMethodIMP(id self, SEL _cmd) {
    // implementation ....
}

@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
    if (aSEL == @selector(resolveThisMethodDynamically)) {
        class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:aSEL];
}
@end

4. 分类添加属性

// 定义关联的key
static const char *key = "name";
@implementation NSObject (Property)
- (NSString *)name
{
    // 根据关联的key,获取关联的值。
    return objc_getAssociatedObject(self, key);
}
- (void)setName:(NSString *)name
{
    // 第一个参数:给哪个对象添加关联
    // 第二个参数:关联的key,通过这个key获取
    // 第三个参数:关联的value
    // 第四个参数:关联的策略
    objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

5. 利用 runtime 获取所有属性来重写归档解档方法

// 设置不需要归解档的属性
- (NSArray *)ignoredNames {
    return @[@"_aaa",@"_bbb",@"_ccc"];
}
// 解档方法
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super initWithCoder:aDecoder]) {
        // 获取所有成员变量
        unsigned int outCount = 0;
        Ivar *ivars = class_copyIvarList([self class], &outCount);

        for (int i = 0; i < outCount; i++) {
            Ivar ivar = ivars[i];
            // 将每个成员变量名转换为NSString对象类型
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];

            // 忽略不需要解档的属性
            if ([[self ignoredNames] containsObject:key]) {
                continue;
            }

            // 根据变量名解档取值,无论是什么类型
            id value = [aDecoder decodeObjectForKey:key];
            // 取出的值再设置给属性
            [self setValue:value forKey:key];
            // 这两步就相当于以前的 self.age = [aDecoder decodeObjectForKey:@"_age"];
        }
        free(ivars);
    }
    return self;
}
// 归档调用方法
- (void)encodeWithCoder:(NSCoder *)aCoder {
    // 获取所有成员变量
    unsigned int outCount = 0;
    Ivar *ivars = class_copyIvarList([self class], &outCount);
    for (int i = 0; i < outCount; i++) {
        Ivar ivar = ivars[i];
        // 将每个成员变量名转换为NSString对象类型
        NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];

        // 忽略不需要归档的属性
        if ([[self ignoredNames] containsObject:key]) {
            continue;
        }

        // 通过成员变量名,取出成员变量的值
        id value = [self valueForKeyPath:key];
        // 再将值归档
        [aCoder encodeObject:value forKey:key];
        // 这两步就相当于 [aCoder encodeObject:@(self.age) forKey:@"_age"];
    }
    free(ivars);
}