Runtime 常用场景

前言:
本文主要介绍一些常用Runtime API的常用场景,用以解决初学者对于Runtime运用上的一些困惑,以便合理的将Runtime运用到项目中
*--- 欢迎指正和补充 ---*

1. 常用场景归纳

    1. 利用关联对象(AssociatedObject)给分类添加属性
    1. 遍历类的所有成员变量或属性,获取私有成员变量信息,以修改私有属性(如:修改textfield的占位文字颜色)
    1. 遍历类的属性(字典转模型、自动归档解档、重写- description等)
    1. 交换方法实现
    • 拦截系统方法,对其进行修改和补充,拓展一些自己的业务逻辑(如监听一些事件等)
    • 当三方pod框架不满足使用场景情况下,可以通过交换方法实现来达到不修改原框架的情况下,实现对业务场景的支持
    1. 其它不常用使用:
    • App唤醒时控制器的万能跳转(动态创建一个控制器然后,传入预定好的参数进行跳转)
    • 热点修复(先动态添加一个方法,然后替换有问题方法的实现)
    • 利用方法调用过程中的消息转发机制,来优化方法找不到的异常问题

2. 应用案例

利用关联对象给TLPerson分类添加nameweight属性

  • 了解更多关于分类的知识
#import 

@interface TLPerson (ExampleCode)

@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) int weight;
@end

@implementation TLPerson (ExampleCode)

- (void)setName:(NSString *)name
{
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name
{
    // 隐式参数
    // _cmd == @selector(name)
    return objc_getAssociatedObject(self, _cmd);
}

- (void)setWeight:(int)weight
{
    objc_setAssociatedObject(self, @selector(weight), @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (int)weight
{
    // _cmd == @selector(weight)
    return [objc_getAssociatedObject(self, _cmd) intValue];
}

@end

修改UITextView实例对象的私有属性

  • 通过class_copyIvarList遍历UITextField的所有成员变量,
// 成员变量的数量
    unsigned int count;
    Ivar *ivars = class_copyIvarList([UITextField class], &count);
    for (int i = 0; i < count; i++) {
        // 取出i位置的成员变量
        Ivar ivar = ivars[i];
        NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
    }
    free(ivars);
  • 可发现它有_placeholderLabel_clearButton两个私有成员
  • 然后可以通过KVC来修改其属性
 // 修改光标颜色(placeholder)
[textField setValue:UIColorFromRGBA(0xcccdcd, 1.f) forKeyPath:@"_placeholderLabel.textColor"]; 

// 修改清除按钮的图片
UIButton *clearButton = [textField valueForKey:@"_clearButton"];
[clearButton setImage:[UIImage imageNamed:@"login_icon_clear"] forState:UIControlStateNormal];

字典转模型、自动归档解档(来自MJExtension节选片段,仅作参考)

    1. 通过class_copyPropertyList获取属性列表
+ (NSMutableArray *)properties
{
    NSMutableArray *cachedProperties = [self propertyDictForKey:&MJCachedPropertiesKey][NSStringFromClass(self)];
    
    if (cachedProperties == nil) {
        MJExtensionSemaphoreCreate
        MJExtensionSemaphoreWait
        
        if (cachedProperties == nil) {
            cachedProperties = [NSMutableArray array];
            
            [self mj_enumerateClasses:^(__unsafe_unretained Class c, BOOL *stop) {
                // 1.获得所有的成员变量
                unsigned int outCount = 0;
                objc_property_t *properties = class_copyPropertyList(c, &outCount);
                
                // 2.遍历每一个成员变量
                for (unsigned int i = 0; i
    1. 遍历properties将字典转为模型
- (instancetype)mj_setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
{
    // 获得JSON对象
    keyValues = [keyValues mj_JSONObject];
    
    MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], self, [self class], @"keyValues参数不是一个字典");
    
    Class clazz = [self class];
    NSArray *allowedPropertyNames = [clazz mj_totalAllowedPropertyNames];
    NSArray *ignoredPropertyNames = [clazz mj_totalIgnoredPropertyNames];
    
    //通过封装的方法回调一个通过运行时编写的,用于返回属性列表的方法。
    [clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
        @try {
            // 0.检测是否被忽略
            if (allowedPropertyNames.count && ![allowedPropertyNames containsObject:property.name]) return;
            if ([ignoredPropertyNames containsObject:property.name]) return;
            
            // 1.取出属性值
            id value;
            NSArray *propertyKeyses = [property propertyKeysForClass:clazz];
            for (NSArray *propertyKeys in propertyKeyses) {
                value = keyValues;
                for (MJPropertyKey *propertyKey in propertyKeys) {
                    value = [propertyKey valueInObject:value];
                }
                if (value) break;
            }
            
            // 值的过滤
            id newValue = [clazz mj_getNewValueFromObject:self oldValue:value property:property];
            if (newValue != value) { // 有过滤后的新值
                [property setValue:newValue forObject:self];
                return;
            }
            
            // 如果没有值,就直接返回
            if (!value || value == [NSNull null]) return;
            
            // 2.复杂处理
            MJPropertyType *type = property.type;
            Class propertyClass = type.typeClass;
            Class objectClass = [property objectClassInArrayForClass:[self class]];
            
            // 不可变 -> 可变处理
            if (propertyClass == [NSMutableArray class] && [value isKindOfClass:[NSArray class]]) {
                value = [NSMutableArray arrayWithArray:value];
            } else if (propertyClass == [NSMutableDictionary class] && [value isKindOfClass:[NSDictionary class]]) {
                value = [NSMutableDictionary dictionaryWithDictionary:value];
            } else if (propertyClass == [NSMutableString class] && [value isKindOfClass:[NSString class]]) {
                value = [NSMutableString stringWithString:value];
            } else if (propertyClass == [NSMutableData class] && [value isKindOfClass:[NSData class]]) {
                value = [NSMutableData dataWithData:value];
            }
            
            if (!type.isFromFoundation && propertyClass) { // 模型属性
                value = [propertyClass mj_objectWithKeyValues:value context:context];
            } else if (objectClass) {
                if (objectClass == [NSURL class] && [value isKindOfClass:[NSArray class]]) {
                    // string array -> url array
                    NSMutableArray *urlArray = [NSMutableArray array];
                    for (NSString *string in value) {
                        if (![string isKindOfClass:[NSString class]]) continue;
                        [urlArray addObject:string.mj_url];
                    }
                    value = urlArray;
                } else { // 字典数组-->模型数组
                    value = [objectClass mj_objectArrayWithKeyValuesArray:value context:context];
                }
            } else {
                if (propertyClass == [NSString class]) {
                    if ([value isKindOfClass:[NSNumber class]]) {
                        // NSNumber -> NSString
                        value = [value description];
                    } else if ([value isKindOfClass:[NSURL class]]) {
                        // NSURL -> NSString
                        value = [value absoluteString];
                    }
                } else if ([value isKindOfClass:[NSString class]]) {
                    if (propertyClass == [NSURL class]) {
                        // NSString -> NSURL
                        // 字符串转码
                        value = [value mj_url];
                    } else if (type.isNumberType) {
                        NSString *oldValue = value;
                        
                        // NSString -> NSNumber
                        if (type.typeClass == [NSDecimalNumber class]) {
                            value = [NSDecimalNumber decimalNumberWithString:oldValue];
                        } else {
                            value = [numberFormatter_ numberFromString:oldValue];
                        }
                        
                        // 如果是BOOL
                        if (type.isBoolType) {
                            // 字符串转BOOL(字符串没有charValue方法)
                            // 系统会调用字符串的charValue转为BOOL类型
                            NSString *lower = [oldValue lowercaseString];
                            if ([lower isEqualToString:@"yes"] || [lower isEqualToString:@"true"]) {
                                value = @YES;
                            } else if ([lower isEqualToString:@"no"] || [lower isEqualToString:@"false"]) {
                                value = @NO;
                            }
                        }
                    }
                } else if ([value isKindOfClass:[NSNumber class]] && propertyClass == [NSDecimalNumber class]){
                    // 过滤 NSDecimalNumber类型
                    if (![value isKindOfClass:[NSDecimalNumber class]]) {
                        value = [NSDecimalNumber decimalNumberWithDecimal:[((NSNumber *)value) decimalValue]];
                    }
                }
                
                // value和property类型不匹配
                if (propertyClass && ![value isKindOfClass:propertyClass]) {
                    value = nil;
                }
            }
            
            // 3.赋值
            [property setValue:value forObject:self];
        } @catch (NSException *exception) {
            MJExtensionBuildError([self class], exception.reason);
            MJExtensionLog(@"%@", exception);
        }
    }];
    
    // 转换完毕
    if ([self respondsToSelector:@selector(mj_keyValuesDidFinishConvertingToObject)]) {
        [self mj_keyValuesDidFinishConvertingToObject];
    }
    if ([self respondsToSelector:@selector(mj_keyValuesDidFinishConvertingToObject:)]) {
        [self mj_keyValuesDidFinishConvertingToObject:keyValues];
    }
    return self;
}
    1. 归档与解档
- (void)mj_encode:(NSCoder *)encoder
{
    Class clazz = [self class];
    
    NSArray *allowedCodingPropertyNames = [clazz mj_totalAllowedCodingPropertyNames];
    NSArray *ignoredCodingPropertyNames = [clazz mj_totalIgnoredCodingPropertyNames];
    // 遍历归档
    [clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
        // 检测是否被忽略
        if (allowedCodingPropertyNames.count && ![allowedCodingPropertyNames containsObject:property.name]) return;
        if ([ignoredCodingPropertyNames containsObject:property.name]) return;
        
        id value = [property valueForObject:self];
        if (value == nil) return;
        [encoder encodeObject:value forKey:property.name];
    }];
}

- (void)mj_decode:(NSCoder *)decoder
{
    Class clazz = [self class];
    
    NSArray *allowedCodingPropertyNames = [clazz mj_totalAllowedCodingPropertyNames];
    NSArray *ignoredCodingPropertyNames = [clazz mj_totalIgnoredCodingPropertyNames];
     // 遍历解档
    [clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
        // 检测是否被忽略
        if (allowedCodingPropertyNames.count && ![allowedCodingPropertyNames containsObject:property.name]) return;
        if ([ignoredCodingPropertyNames containsObject:property.name]) return;
        
        id value = [decoder decodeObjectForKey:property.name];
        if (value == nil) { // 兼容以前的MJExtension版本
            value = [decoder decodeObjectForKey:[@"_" stringByAppendingString:property.name]];
        }
        if (value == nil) return;
        [property setValue:value forObject:self];
    }];
}

同交换方法实现
eg1. 给UIViewController分类中使用系统原生API,如:- dealloc方法
eg2. 解决给数组添加nil时奔溃问题;

// eg1. 给UIViewController的的分类中使用`- dealloc`方法
@implementation UIViewController (TLTransition)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class cls = [UIViewController class];
         //
        // 注意直接使用:@selector(dealloc) 会报错:ARC forbids use of 'dealloc' in a @selector
        // Method method = class_getInstanceMethod(cls, @selector(dealloc));
        // 使用字符串获取NSSelectorFromString(@"dealloc")
        Method method1 = class_getInstanceMethod(cls, NSSelectorFromString(@"dealloc"));
        Method method2 = class_getInstanceMethod(cls, @selector(tl_dealloc));
        method_exchangeImplementations(method1, method2);
    });
}

- (void)tl_dealloc{
   // do something
    tl_Log(@"%@ %s", [self class], __func__);
    [TLTransitionDelegate removeAnimatorForKey:self];
    
    // 回到原来的dealloc方法 
    [self tl_dealloc];
}

@end
// eg2. 解决给数组添加nil时奔溃问题;
@implementation NSMutableArray (Extension)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 类簇:NSString、NSArray、NSDictionary,真实类型是其他类型(如:NSMutableArray 真实类型是 __NSArrayM)
        Class cls = NSClassFromString(@"__NSArrayM");
        Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
        Method method2 = class_getInstanceMethod(cls, @selector(swizzling__insertObject:atIndex:));
        method_exchangeImplementations(method1, method2);
    });
}

- (void)swizzling__insertObject:(id)anObject atIndex:(NSUInteger)index
{
    if (anObject == nil) return;
    
    [self swizzling__insertObject:anObject atIndex:index];
}
@end

你可能感兴趣的:(Runtime 常用场景)