Runtime的简单使用

Runtime简介

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

相关函数

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

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

Runtime应用

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

//根据运行时动态的获取模型属性名称和类型;
+(NSMutableArray * )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

实现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

打印结果:

_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====@""
_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 
@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的简单使用)