学学人家的框架平时收藏算法或者代码

Runtime整理

2016-05-01  本文已影响496人  8cd16d46a1c6

runtime一致蒙着一层神秘的面纱,在面试中,90%的技术都会问道相关的问题,目的为了考察OC中学习的深度.这里是我们自己学习runtime的一些理解,觉得有用的朋友可以借鉴下,大神们路过就好

Demo地址:https://github.com/zrSunshine/runtimeDemo.git

列举几个常用的运行时方法

#import <objc/runtime.h>

// 获得某个类的类方法
 Method class_getClassMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>)

// 获得某个类的对象方法
Method class_getInstanceMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>)

// 方法交换
void method_exchangeImplementations(<#Method m1#>, <#Method m2#>)

// 拷贝某个类的所有成员变量
class_copyIvarList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>)

// 设置关联对象
objc_setAssociatedObject(<#id object#>, <#const void *key#>, <#id value#>, <#objc_AssociationPolicy policy#>)

// 获取关联对象
objc_getAssociatedObject(<#id object#>, <#const void *key#>)

// 给某个对象发送某个消息
void objc_msgSend(void /* id self, SEL op, ... */ )

1. runtime是一套纯C语言的API(纯C语言的库)

2. 利用运行时,可以做很多底层的操作,比如:

获得某个类的所有成员方法,所有成员变量
     ///  获的某个类的所有成员变量

    ///  参数1:要获取的类
    ///  参数2:成员变量的个数 (这里需要传一个`unsigned int`类型变量地址)
    ///
    ///  @return Ivar * (返回一个`Ivar *`类型的指针,里面装满了这个类的成员变量,类似一个数组,但不是数组,应该说它是以个结构体指针)
    
     Ivar *ivars = class_copyIvarList(__unsafe_unretained Class cls, unsigned int *outCount);
    
    * 方法描述:可以这样来理解这个方,拷贝某个类`参数1`的成员变量列表,当这个方法执行完时,我们可以得到这个类的所有成员变量个数保存在`unsigned int`类型的变量`参数2`中.返回一个`Ivar *`类型的指针,里面装满了这个类的成员变量.

举例

    首先定义一个变量来保存成员变量个数
    unsigned int outCount = 0;
        
    Ivar *ivars = class_copyIvarList([Person class], &outCount);
    
    关于参数2为什么要传一个地址:有人可能会疑惑,来记录个数,传一个outCount不就行了,为什么要传&outCount,
    如果,我们直接把变量outCount传进去,首先会有警告
    其次,直接传outCount就成了值传递,等于将 0 这个值传到了函数中,执行完函数后,外面记录成员变量总数的变量值是不会改变的.
    因此,这里将outCount的地址穿进去,在函数执行过程中通过&outCount地址访问外部变量,修改变量的值并保存起来

函数说明

利用runtime,实现自动归档与自动解档

案例二


增加忽略属性方法

补充:NSObject + Extension 抽取宏

小结

    **子类也可以归档父类属性,重点就在这里**
    
     Class currentClass = self.class;
    // 如果当前类不是NSObject这个类,就实现归档
    while (currentClass && currentClass != [NSObject class])
    
    首先获得当前对象的类,self.class
    然后用while循环来判断,当前类currentClass如果不是NSObject的情况下,才需要进行解归档操作,NSObject是最基类,它没有父类,如果判断是NSObject就表示不用再归档父类属性
    
    // 重新赋值当前的类
    currentClass = [currentClass superclass];
    程序执行到最后,重新给当前类赋值为它的父类,这样就可以持续循环找到父类的属性来解归档

动态交换两个方法的实现(特别是交换系统自带的方法)
     如:我们利用运行时,可以在调用start方法的时候,实现调用到stop方法,但是,这有什么用呢?
    
    start {
         开始
    }
    
    stop {
         停止
    }
    
    
    * 试想,我们可以拦截系统的某些方法,来实现我们需要平时无法完成的事
    
    如:
    我们自己写一个alloc方法,把系统的alloc方法换了,在内部实现计数器+1,那么只要alloc创建对象的时就会调用到我们自己实现的alloc方法总,我们就可以知道我们内存中有多少个对象
    
    
    我们也可以拦截系统的[UIImage imageNamed:]方法,将系统方法给换了,那么我么就可以知道我们在整个项目中加载了多少图片,总共内存有多少
runtime运用举例
    ** 我们只需要在UIImage的分类中实现以下方法就可以轻松实现我们的需求 **
    
    + (void)load {  //当系统加载到UIImage这个类时,我么做方法的交换
    
    // 获得需要交换的两个方法
    Method mSys = class_getClassMethod([UIImage class], @selector(imageNamed:));
    Method mMy = class_getClassMethod([UIImage class], @selector(px_imageName:));
    
    // 交换方法的实现
    method_exchangeImplementations(mSys, mMy);

    }
    

    // 自定义一个方法,准备用来与系统方法交换
    +(UIImage *)px_imageName:(NSString *)imageName {
    
        double version = [[UIDevice currentDevice].systemVersion doubleValue];
        if (version >= 7.0) {
            imageName = [imageName stringByAppendingString:@"_os7"];
        }
        
        // 调回系统的方法
        // 这里需要调用系统方法来赋值,我们就应该调用自己的方法交换到系统的方法
        return [UIImage px_imageName:imageName];
    }

注意:

需要注意的是:如果我们直接通过分类方法来实现`imageNamed:`方法是不行的,系统会报警告

“Category is implementing a method which will also be implemented by its primary class” ---> 我在category中重写了原类的方法 而苹果的官方文档中明确表示  我们不应该在category中复写原类的方法,如果要重写 请使用继承

原文是这样的: category allows you to add new methods to an existing class. If you want to reimplement a method that already exists in the class, you typically create a subclass instead of a category. 

我们在这里并没有重写系统的原生的方法,我们只是使用自己的方法实现了要做的事,然后又调用回系统原生的方法,这样我们没有改变系统原生的内部实现,对系统本身并没有伤害

3. 编译时最终会将OC的代码转换为 运行时代码


4. 运行时的应用,分类增加属性

objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
描述这句代码:将值 value 通过 key 与对象 objc 关联起来 (将 value 存储到 objc 中)

** 参数说明 **

<id object> :需要存值的对象 (哪个对象调用就表示哪个对象需要把值关联起来)

<const void *key> :根据这个key来存取值,这个key,与字典一样,一个key对应保存一个value,而void * 与id类型相似,表示这个key什么都可以用,但是推荐使用char,节省空间

<id value> :要存储的值 (要关联的值)

<objc_AssociationPolicy policy> :存储策略 (表示用什么方式来引用存储的值,如assign ,copy , retain就,strong)

objc_getAssociatedObject(id object, const void *key)
描述这句代码:通过 key 从关联对象 objct 中获取值
举例

5.实现字典和模型的转换

小结

上面的小例子中,我们的字典中键值对比模型中属性多一组键值对

如果,使用KVC的情况下系统会直接crash,但是我们用runtime实现的方法执行过程是与KVC相反的,我们都知道KVC是从字典下手,从字典中把每一个键对应的值取出来,给模型属性赋值,而我们用runtime实现的方法执行过程是先遍历模型属性,模型中有的属性我们采取字典中找,如果模型中没有,我们就不去找,那么就不会出现不匹配的情况

字典嵌套字典

小结

1.在模型嵌套模型的情况中,我们首先要分析数据,谁包含了谁,如:Person中包含了Dog,Dog类中又是一个字典,那么我首先由内向外,一层层转换,先将Dog中的字典转换为模型,在来搞外层字典.

2.这里用到了`ivar_getTypeEncoding(ivar)`来获取属性的类型,是为了区别于其他的属性,用查询字符串和截取字符串的方法一步步接近我们要的目标,最终获得我们想要的类

3.拿到内层的类后,取出这个类对应的字典value,做一次转模型,最后将得到的模型设置给对应的属性   

字典嵌套数组

    - (void)setDict:(NSDictionary *)dict {
    
        Class currentClass = self.class;
    
        while (currentClass && currentClass != [NSObject class]) {
            
            unsigned int outCount = 0;
            // 获取类的所有成员变量
            Ivar *ivars = class_copyIvarList(currentClass, &outCount);
            // 逐个获取
            for (int i = 0; i < outCount; i++) {
                Ivar ivar = ivars[i];
                
                // 获得类的所有属性
                NSString *key = @(ivar_getName(ivar));
                key = [key substringFromIndex:1];
                // 取出字典
                id value = dict[key];
                // 如果字典中没有对应属性的值
                if (value == nil) continue;
                
                // 获取当前属性的类型
                NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
               
    
                // 查询类型中是否有"@"
                NSRange range = [type rangeOfString:@"@"];
                
                if (range.location != NSNotFound) {  // 能找到"@"
                    type = [type substringWithRange:NSMakeRange(2, type.length - 3)];
    
                    if (![type hasPrefix:@"NS"]) {
                       Class class = NSClassFromString(type);
                        value = [class objectWithDict:value];
    
                    } else {
                    
                    //  这里如果判断是带NS的前缀才会进入
                    
                    *******变化的地方********
                        
                        Class class = NSClassFromString(type);
                        if ([class isSubclassOfClass:[NSArray class]]) {
                            
                            // 获得数组
                            NSArray *array = (NSArray *)value;
                            
                            NSMutableArray *arrayM = [NSMutableArray array];
                            
                            // 数组里面装的都是Book类型的字典
                            for (NSDictionary *dict in array) {
                             
                                // 将字典转模型
                                Book *book = [Book objectWithDict:dict];
                                
                                [arrayM addObject:book];
                            }
                            
                            value = arrayM;
                        }
                        
                  ***********变化的地方************
                  
                    }
                }
    
                [self setValue:value forKeyPath:key];
            }
            // 释放
            free(ivars);
            // 重新赋值当前的类
            currentClass = [currentClass superclass];
        }
    }

小结

1.在字典嵌套数组的情况中,我们需要首先创建一个对应数组的模型类出来

2.判断遍历的到key对应的type是否是NSArray类型

3.获得key对应的value

4.将value转换为模型赋值给对应的属性

Demo地址:下载地址


整理这篇文章,主要是为了给自己整理下思路,也是作为一个笔记,以备以后忘了,可以常来看看,希望也能帮助到刚接触运行时基本方法的朋友,最后感谢杰哥,借鉴了部分滕先洪的runtime,谢谢

上一篇下一篇

猜你喜欢

热点阅读