美文网首页
Runtime的简单使用

Runtime的简单使用

作者: Mark_Guan | 来源:发表于2016-11-23 18:18 被阅读136次

    Runtime简介

    Runtime是一套底层的C语言API(包含了很多强大实用的C语言数据类型和C语言函数), 实际上,平时我们编写的OC代码,底层都是基于Runtime实现的,也就是说,平时我们编写的OC代码,最终都是转成了底层的Runtime代码。
    对于C语言,函数的调用会在编译阶段决定调用哪个函数
    对于OC的函数,属于动态调用过程的,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。

    相关函数

    对对象进行操作的方法一般以`object_`开头
    对类进行操作的方法一般以`class_`开头
    对类或对象的方法进行操作的方法一般以`method_`开头
    对成员变量进行操作的方法一般以`ivar_`开头
    对属性进行操作的方法一般以`property_`开头开头
    对协议进行操作的方法一般以`protocol_`开头
    

    根据以上的函数的前缀 可以大致了解到层级关系。对于以objc_开头的方法,则是runtime`最终的管家,可以获取内存中类的加载信息,类的列表,关联对象和关联属性等操作。

    Runtime应用

    动态的获取模型属性和模型属性类型
    之前封装了一个简单的自动化类库,根据模型的变动来决定表结构。如果模型中多了一个属性,那么对应的数据表中就要多一个对应的字段,同理,如果模型中有一个属性我不要了,那么表结构对应的字段也要删掉。这里就是运用了运行时,动态的去获取模型的属性和属性类型,通过比对,如果有变化则进行更新操作。
    下面,贴一段简单的代码:

    //根据运行时动态的获取模型属性名称和类型;
    +(NSMutableArray<NSString * > * )getModelNameType:(Class)cls
    {
        unsigned int outCount;
        Ivar * ivarList=class_copyIvarList(cls, &outCount);
        NSMutableArray * muArray=[NSMutableArray array];
        
        //获取用户不想创建的字段;
        NSArray * ignoreArray;
        if ([cls instancesRespondToSelector:@selector(ignorePropertyNames)]) {
            ignoreArray=[[cls new] ignorePropertyNames];
        }
        
        for (int i=0; i<outCount; i++) {
            Ivar ivar=ivarList[i];
            
            //获取属性名称;
            NSString * propertyName=[[NSString stringWithUTF8String:ivar_getName(ivar)] substringFromIndex:1];
            
            //获取属性类型;
            NSString * propertyType=[[NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"@\"^"]];
            
            if(![ignoreArray containsObject:propertyName])
            {
                //将OC类型转换为sql数据类型;
                NSString * sqlType=[self sqlTypeToRuntimeTypeDic][propertyType];
                
                NSString * str=[NSString stringWithFormat:@"%@ %@",propertyName,sqlType];
                [muArray addObject:str];
            }
        }
        return  muArray;
    }
    

    实现Pop全屏手势
    添加全屏Pop手势我能够想到的有两种思路:

    1. 自己在push出来的View中添加UIPanGestureRecognizer手势。监听手势滑动,随着手势滑动,逐渐退出控制器的View,但是这种方式实现起来感觉较为麻烦。
    2. 利用运行时机制,获取系统的Pop手势的targetaction,创建自己的手势,同时给自己创建的手势添加监听事件的时候使用上面获得的targetaction即可。下面就以第二种方式来实现:

    因为系统UIPanGestureRecognizer手势中没有暴露出这个targetaction,所以我们可以通过利用运行时机制来获取遍历:
    首先我们自定义一个继承自 UINavigationController的类,在该类中进行操作:

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        //获取系统滑动手势
        UIGestureRecognizer * systemGes =self.interactivePopGestureRecognizer;
        
        //获取系统手势所添加的View
        UIView * vc=systemGes.view;
        
        unsigned int count= 0;
        
        //class_copyIvarList:把成员属性列表复制一份出来
        Ivar *  ivarList= class_copyIvarList([UIGestureRecognizer class], &count);
        
        for (int i=0; i<count; i++) {
        
             // 获取成员属性
            Ivar ivar=ivarList[i];
            
            //ivar_getName:获取成员名
            NSString * propertyName=[NSString stringWithUTF8String:ivar_getName(ivar)];
            
            //ivar_getTypeEncoding:获取成类型
            NSString * propertyType=[NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
            
            NSLog(@"%@====%@",propertyName,propertyType);
        }
    }
    

    打印结果:

    _targets====@"NSMutableArray"
    _delayedTouches====@"NSMutableArray"
    _delayedPresses====@"NSMutableArray"
    _view====@"UIView"
    _lastTouchTimestamp====d
    _state====q
    _allowedTouchTypes====q
    _initialTouchType====q
    _internalActiveTouches====@"NSMutableSet"
    _forceClassifier====@"_UIForceLevelClassifier"
    _requiredPreviewForceState====q
    _touchForceObservable====@"_UITouchForceObservable"
    "NSObservation"
    _forceTargets====@"NSMutableArray"
    _forcePressCount====Q
    _beganObservable====@"NSObservationSource"
    _failureRequirements====@"NSMutableSet"
    _failureDependents====@"NSMutableSet"
    _delegate====@"<UIGestureRecognizerDelegate>"
    _allowedPressTypes====@"NSArray"
    _gestureEnvironment====@"UIGestureEnvironment"

    好了,重点来了,我们可以看到第一个属性名是_targets,类型是NSMutableArray,我们来打印下看看这个_targets是个什么鬼

        NSMutableArray * array=[systemGes valueForKeyPath:@"_targets"];
        
        NSLog(@"%@",array);
    

    打印结果:

    (
        "(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x7ff9f3500da0>)"
    )
    

    可以看到这个数组里面只有这一项,并且终于看到了我们想要的actiontarget,这就好办了,只要我们拿到这两个鬼,然后添加到我们自己创建的手势上就大功告成了。

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        //获取系统滑动手势
        UIGestureRecognizer * systemGes =self.interactivePopGestureRecognizer;
        
        //获取系统手势所添加的View
        UIView * vc=systemGes.view;
        
        unsigned int count= 0;
        
        //class_copyIvarList:把成员属性列表复制一份出来
        Ivar *  ivarList= class_copyIvarList([UIGestureRecognizer class], &count);
    
        NSMutableArray * array=[systemGes valueForKeyPath:@"_targets"];
        
        //取出_targets对象。
        id targetObjct= array[0];
        
        //取出target
        id target = [targetObjct valueForKeyPath:@"target"];
    
        //获取action
        SEL action = NSSelectorFromString(@"handleNavigationTransition:");
        
        //创建属于我们自己的手势
        UIPanGestureRecognizer * ges=[[UIPanGestureRecognizer alloc]initWithTarget:target action:action];
    
        [vc addGestureRecognizer:ges];
        
    }
    

    字典转模型

    字典转模型KVC实现
    KVC:遍历字典中所有key,去模型中查找有没有对应的属性。这个是要求字典中的Key,必须要在模型里能找到相应的值,如果找不到就会报错。基本原理如下:

        // KVC原理:遍历字典中所有key,去模型中查找有没有对应的属性
        [dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull value, BOOL * _Nonnull stop) {
            
            // 去模型中查找有没有对应属性 KVC
            // key:source value:baidu
            // [item setValue:@"baidu" forKey:@"source"]
            [item setValue:value forKey:key];
        }];
    

    但是,在实际开发中,从字典中取值,不一定要全部取出来。因此,我们可以通过重写KVC中的forUndefinedKey这个方法,就不会进行报错处理。

    // 解决KVC报错
    - (void)setValue:(id)value forUndefinedKey:(NSString *)key
    {
        //如果字典中的key是 id  我们就将 id对应的值赋值给 ID  ,相当于做了一个转换
        if ([key isEqualToString:@"id"]) {
            _ID = [value integerValue];
        }
        // key:没有找到key value:没有找到key对应的值
        NSLog(@"%@ %@",key,value);
    }
    

    字典转模型Runtime实现
    KVC是通过遍历字典中所有key,去模型中查找有没有对应的属性。而Runtime的思路是通过遍历模型的值,从字典中取值。是不是与KVC反过来了。

    + (instancetype)modelWithDict:(NSDictionary *)dict
    {
        id objc = [[self alloc] init];
       
        // count:成员变量个数
        unsigned int count = 0;
        // 获取成员变量数组
        Ivar *ivarList = class_copyIvarList(self, &count);
        
        // 遍历所有成员变量
        for (int i = 0; i < count; i++) {
            // 获取成员变量
            Ivar ivar = ivarList[i];
            
            // 获取成员变量名字
            NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
            // 获取成员变量类型
            NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
            // @\"User\" -> User
            ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
            ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
            // 获取key
            NSString *key = [ivarName substringFromIndex:1];
            
            // 去字典中查找对应value
            // key:user  value:NSDictionary
            
            id value = dict[key];
            
            // 二级转换:判断下value是否是字典,如果是,字典转换层对应的模型
            // 并且是自定义对象才需要转换
            if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) {
                // 字典转换成模型 userDict => User模型
                // 转换成哪个模型
    
                // 获取类
                Class modelClass = NSClassFromString(ivarType);
                
                value = [modelClass modelWithDict:value];
            }
            
            // 给模型中属性赋值
            if (value) {
                [objc setValue:value forKey:key];
            }
        }
            
        return objc;
    }
    

    交换方法

    交换方法实现的需求场景:自己创建了一个功能性的方法,在项目中多次被引用,当项目的需求发生改变时,要使用另一种功能来代替这个功能,但是最好是不改变旧的项目。

    可以在类的分类中,再写一个新的方法(是符合新的需求的),然后交换两个方法的实现。这样,在不改变项目的代码,而只是增加了新的代码 的情况下,就完成了项目的改进。

    交换两个方法的实现一般写在类的load方法里面,因为load方法会在程序运行前加载一次,而initialize方法会在类或者子类在 第一次使用的时候调用,当有分类的时候会调用多次。

    比如:给imageNamed方法提供功能,每次加载图片就判断下图片是否加载成功。

    
    @implementation UIImage (Image)
    // 加载分类到内存的时候调用
    + (void)load
    {
        // 获取imageWithName方法地址
        Method imageWithName = class_getClassMethod(self, @selector(imageWithName:));
        
        // 获取imageNamed方法地址
        Method imageName = class_getClassMethod(self, @selector(imageNamed:));
        
        // 交换方法地址,相当于交换实现方式
        method_exchangeImplementations(imageWithName, imageName);
    }
    
    // 既能加载图片又能打印错误信息
    + (instancetype)imageWithName:(NSString *)name
    {
        // 这里调用imageWithName,相当于调用imageName
        UIImage *image = [self imageWithName:name];
        
        if (image == nil) {
            NSLog(@"加载空的图片");
        }
        
        return image;
    }
    
    @end
    

    补充

    字典转模型的第一步是要先有一个模型对象,而模型属性通常需要跟字典中的key一一对应
    问题:一个一个的生成模型属性,很慢?
    需求:能不能自动根据一个字典,生成对应的属性。
    解决:提供一个分类,专门根据字典生成对应的属性字符串

    #import <Foundation/Foundation.h>
    @interface NSObject (Property)
    + (void)createPropertyCodeWithDict:(NSDictionary *)dict;
    @end
    
    #import "NSObject+Property.h"
    
    @implementation NSObject (Property)
    
    + (void)createPropertyCodeWithDict:(NSDictionary *)dict
    {
        
        NSMutableString *strM = [NSMutableString string];
    
        // 遍历字典
        [dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull propertyName, id  _Nonnull value, BOOL * _Nonnull stop) {
            //        NSLog(@"%@ %@",propertyName,[value class]);
            NSString *code;
    
            if ([value isKindOfClass:NSClassFromString(@"__NSCFString")]) {
                code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSString *%@;",propertyName]
                ;
            }else if ([value isKindOfClass:NSClassFromString(@"__NSCFNumber")]){
                code = [NSString stringWithFormat:@"@property (nonatomic, assign) int %@;",propertyName]
                ;
            }else if ([value isKindOfClass:NSClassFromString(@"__NSCFArray")]){
                code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSArray *%@;",propertyName]
                ;
            }else if ([value isKindOfClass:NSClassFromString(@"__NSCFDictionary")]){
                code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSDictionary *%@;",propertyName]
                ;
            }else if ([value isKindOfClass:NSClassFromString(@"__NSCFBoolean")]){
                code = [NSString stringWithFormat:@"@property (nonatomic, assign) BOOL %@;",propertyName]
                ;
            }
            [strM appendFormat:@"\n%@\n",code];
            
        }];
        
         NSLog(@"%@",strM);
    }
    
    @end
    

    这样NSLog打印出来的就是后台返回给你的模型属性,不用手动在一个个的敲了,是不是省去很多麻烦?

    相关文章

      网友评论

          本文标题:Runtime的简单使用

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