美文网首页
Runtime 常用场景

Runtime 常用场景

作者: Gxdy | 来源:发表于2018-09-30 15:13 被阅读0次

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

    1. 常用场景归纳

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

    2. 应用案例

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

    #import <objc/runtime.h>
    
    @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<outCount; i++) {
                        MJProperty *property = [MJProperty cachedPropertyWithProperty:properties[i]];
                        // 过滤掉Foundation框架类里面的属性
                        if ([MJFoundation isClassFromFoundation:property.srcClass]) continue;
                        property.srcClass = c;
                        [property setOriginKey:[self propertyKey:property.name] forClass:self];
                        [property setObjectClassInArray:[self propertyObjectClassInArray:property.name] forClass:self];
                        [cachedProperties addObject:property];
                    }
                    
                    // 3.释放内存
                    free(properties);
                }];
                
                [self propertyDictForKey:&MJCachedPropertiesKey][NSStringFromClass(self)] = cachedProperties;
            }
            
            MJExtensionSemaphoreSignal
        }
        
        return cachedProperties;
    }
    
      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 常用场景

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