美文网首页
iOS运行时(Runtime) 常用总结

iOS运行时(Runtime) 常用总结

作者: 路小白同学 | 来源:发表于2018-04-19 17:27 被阅读0次

    runtime是一套底层的C语言API,包含很多强大实用的C语言数据类型和C语言函数,平时我们编写的OC代码,底层都是基于runtime实现的。它和runloop组合在一起使OC具有了面向对象功能。
    本文主要介绍runtime的一些常用用法。将所用的的方法封装到一个工具里
    本文主要介绍以下几个方面:
    1.动态添加一个类
    2.交换方法实现
    3.获取类的属性列表
    4.给分类动态添加属性
    5.字典转模型
    6.实现NSCoding的自动归档和解档
    7.获取类的实例方法列表 getter,setter,对象方法等
    8.获取协议列表

    1.动态添加一个类
    调用的方法是

      class_addMethod(<#Class  _Nullable __unsafe_unretained cls#>, <#SEL  _Nonnull name#>, <#IMP  _Nonnull imp#>, <#const char * _Nullable types#>)
    

    class_addMethod方法有个参数,第一个参数是要添加方法的类,第二个参数是方法的SEL 可以理解为方法编号,一个SEL对应一个IMP,第三个参数则是提供方法实现的IMP,也就是implementation,是一个指针,指向方法的具体实现。第四个参数是方法的类型,一般写的是"V@:@",V表示返回值是Void,@表示是OC对象,:表示调用@selector方法。
    方法实现如下:

    /**
     往类上添加相应的方法与其实现
     @param fromclass 方法实现的类
     @param class 绑定方法的类
     @param methodSel 方法名
     @param methodselImp 对应方法实现的方法名
     */
    + (void)addMethodWithClass:(Class)class fromClass:(Class)fromclass method:(SEL)methodSel methodImp:(SEL)methodselImp
    {
        Method method = class_getInstanceMethod(fromclass, methodSel);
        IMP methodIMP = method_getImplementation(method);
        const char *types = method_getTypeEncoding(method);
        class_addMethod(class, methodSel, methodIMP, types);
    }
    

    2.交换方法实现
    SEL 和IMP的关系是一一对应的,如图


    sel.png

    方法交换其实也就是交换了他们的IMP,如图:


    exchangeImp.png

    实现代码为:

    /**
     方法交换
    
     @param selone 方法1
     @param selTwo 方法2
     */
    + (void)exchangeMethodWithClsas:(Class)class Sel:(SEL)selone selTwo:(SEL)selTwo;
    {
        Method firstMethod = class_getInstanceMethod(class, selone);
        Method secondMethod = class_getInstanceMethod(class, selTwo);
        method_exchangeImplementations(firstMethod, secondMethod);
    }
    

    3.获取类的属性列表
    使用了class_copyPropertyList(Class,&count)来获取的属性列表,然后通过for循环通过property_getName()来获取每个属性的名字。当然使用property_getName()获取到的名字依然是C语言的char类型的指针,所以我们还需要将其转换成NSString类型,然后放到数组中一并返回。
    代码如下:

    /**
     获取类的属性列表 包括使用属性和公有属性 以及定义在延展中的属性
    
     @param class 类
     @return 数组 里面是字符串
     */
    + (NSArray *)getPropertyWithClass:(Class)class
    {
        unsigned int count = 0;
        objc_property_t *propertyList = class_copyPropertyList(class, &count);
        NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
        for (int i = 0; i < count; i++)
        {
            const char *propertyName = property_getName(propertyList[i]);
            [mutableList addObject:[NSString stringWithUTF8String:propertyName]];
        }
        free(propertyList);
        
        return [NSArray arrayWithArray:mutableList];
    }
    

    4.给分类动态添加属性
    系统 NSObject 添加一个分类,我们知道在分类中是不能够添加成员属性的,虽然我们用了@property,但是仅仅会自动生成get和set方法的声明,并没有带下划线的属性和方法实现生成。但是我们可以通过runtime就可以做到给它方法的实现。
    新建一个测试的分类UIImage+Test
    代码如下:
    .h

    #import <UIKit/UIKit.h>
    
    @interface UIImage (Test)
    
    @property (nonatomic,strong)NSString *urlImage;
    
    @end
    

    .m

    #import "UIImage+Test.h"
    #import <objc/runtime.h>
    @implementation UIImage (Test)
    
    - (NSString *)urlImage
    {
        return objc_getAssociatedObject(self,  (const void*)@"urlImage");
    }
    - (void)setUrlImage:(NSString *)urlImage
    {
        // objc_setAssociatedObject(将某个值跟某个对象关联起来,将某个值存储到某个对象中)
        
        // object:给哪个对象添加属性
        
        // key:属性名称
        
        // value:属性值
        
        // policy:保存策略
        objc_setAssociatedObject(self, (const void*)@"urlImage", urlImage, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    @end
    

    5.字典转模型
    转载至: https://juejin.im/post/593f77085c497d006ba389f0
    字典转模型的方式:
    一个一个的给模型属性赋值(初学者)。
    字典转模型KVC实现

    KVC 字典转模型弊端:必须保证,模型中的属性和字典中的key 一一对应。
    如果不一致,就会调用[ setValue:forUndefinedKey:] 报key找不到的错。

    分析:模型中的属性和字典的key不一一对应,系统就会调用setValue:forUndefinedKey:报错。
    解决:重写对象的setValue:forUndefinedKey:,把系统的方法覆盖,就能继续使用KVC,字典转模型了。

    字典转模型 Runtime 实现

    思路:利用运行时,遍历模型中所有属性,根据模型的属性名,去字典中查找key,取出对应的值,给模型的属性赋值(从提醒:字典中取值,不一定要全部取出来)。

    考虑情况:
    1.当字典的key和模型的属性匹配不上。
    2.模型中嵌套模型(模型属性是另外一个模型对象)。
    3.数组中装着模型(模型的属性是一个数组,数组中是一个个模型对象)。

    注解:根据上面的三种特殊情况,先是字典的key和模型的属性不对应的情况。不对应有两种,一种是字典的键值大于模型属性数量,这时候我们不需要任何处理,因为runtime是先遍历模型所有属性,再去字典中根据属性名找对应值进行赋值,多余的键值对也当然不会去看了;另外一种是模型属性数量大于字典的键值对,这时候由于属性没有对应值会被赋值为nil,就会导致crash,我们只需加一个判断即可。考虑三种情况下面一一注解;

    步骤:提供一个NSObject分类,专门字典转模型,以后所有模型都可以通过这个分类实现字典转模型。

    MJExtension 字典转模型实现

    底层也是对 runtime 的封装,才可以把一个模型中所有属性遍历出来。(你之所以看不懂,是 MJ 封装了很多层而已_.)。

    这里针对字典转模型 KVC 实现,就不做详解了,如果你 对 KVC 详解使用或是实现原理 不是很清楚的,可以参考 实用「KVC编码 & KVO监听

    字典转模型 Runtime 方式实现:

    说明:下面这个示例,是考虑三种情况包含在内的转换示例,具体可以看图上的注解

    Runtime 字典转模型
    5-1、runtime 字典转模型-->字典的 key 和模型的属性不匹配「模型属性数量大于字典键值对数」,这种情况处理如下:

    // Runtime:根据模型中属性,去字典中取出对应的value给模型属性赋值

    // 思路:遍历模型中所有属性->使用运行时

    代码为:

    + (instancetype)modelWithDict:(NSDictionary *)dict
    {
        
        // 1.创建对应的对象
        
        id objc = [[self alloc] init];
        
        
        
        // 2.利用runtime给对象中的属性赋值
        
        /**
         
         class_copyIvarList: 获取类中的所有成员变量
         
         Ivar:成员变量
         
         第一个参数:表示获取哪个类中的成员变量
         
         第二个参数:表示这个类有多少成员变量,传入一个Int变量地址,会自动给这个变量赋值
         
         返回值Ivar *:指的是一个ivar数组,会把所有成员属性放在一个数组中,通过返回的数组就能全部获取到。
         
         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)];
            
            
            
            // 处理成员变量名->字典中的key(去掉 _ ,从第一个角标开始截取)
            
            NSString *key = [ivarName substringFromIndex:1];
            
            
            
            // 根据成员属性名去字典中查找对应的value
            
            id value = dict[key];
            
            
            
            // 【如果模型属性数量大于字典键值对数理,模型属性会被赋值为nil】
            
            // 而报错 (could not set nil as the value for the key age.)
            
            if (value) {
                
                // 给模型中属性赋值
                
                [objc setValue:value forKey:key];
                
            }
            
            
            
        }
        
        
        
        return objc;
        
    }
    

    注:
    这里在获取模型类中的所有属性名,是采取 class_copyIvarList 先获取成员变量(以下划线开头) ,然后再处理成员变量名->字典中的key(去掉 _ ,从第一个角标开始截取) 得到属性名。
    原因:
    Ivar:成员变量,以下划线开头,Property 属性
    获取类里面属性 class_copyPropertyList
    获取类中的所有成员变量 class_copyIvarList

    {
    
        int _a; // 成员变量
    
    }
    
    @property (nonatomic, assign) NSInteger attitudes_count; // 属性
    
    

    这里有成员变量,就不会漏掉属性;如果有属性,可能会漏掉成员变量;
    使用runtime字典转模型获取模型属性名的时候,最好获取成员属性名Ivar因为可能会有个属性是没有setter和``getter方法的。

    5-2.runtime 字典转模型-->模型中嵌套模型「模型属性是另外一个模型对象」,这种情况处理如下:

    /**
     字典转模型 模型中包括模型
    
     @param dict 字典
     @return 结果
     */
    + (instancetype)modelWithDict2:(NSDictionary *)dict
    
    {
        
        // 1.创建对应的对象
        
        id objc = [[self alloc] init];
        
        
        
        // 2.利用runtime给对象中的属性赋值
        
        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
            
            id value = dict[key];
            
            
            
            //--------------------------- 我是分割线 ------------------------------//
            
            //
            
            // 二级转换:如果字典中还有字典,也需要把对应的字典转换成模型
            
            // 判断下value是否是字典,并且是自定义对象才需要转换
            
            if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) {
                
                
                
                // 字典转换成模型 userDict => User模型, 转换成哪个模型
                
                // 根据字符串类名生成类对象
                
                Class modelClass = NSClassFromString(ivarType);
                
                
                
                if (modelClass) { // 有对应的模型才需要转
                    
                    // 把字典转模型
                    
                    value = [modelClass modelWithDict2:value];
                    
                }
                
            }
            
            
            
            // 给模型中属性赋值
            
            if (value) {
                
                [objc setValue:value forKey:key];
                
            }
            
        }
        
        return objc;
        
    }
    
    

    5-3.runtime 字典转模型-->数组中装着模型「模型的属性是一个数组,数组中是字典模型对象」,这种情况处理如下:
    Runtime:根据模型中属性,去字典中取出对应的value给模型属性赋值
    思路:遍历模型中所有属性->使用运行时

    
    + (instancetype)modelWithDict3:(NSDictionary *)dict
    
    {
        
        // 1.创建对应的对象
        
        id objc = [[self alloc] init];
        
        
        
        // 2.利用runtime给对象中的属性赋值
        
        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)];
            
            
            
            // 处理成员属性名->字典中的key(去掉 _ ,从第一个角标开始截取)
            
            NSString *key = [ivarName substringFromIndex:1];
            
            
            
            // 根据成员属性名去字典中查找对应的value
            
            id value = dict[key];
            
            
            
            
            
            //--------------------------- 我是分割线 ------------------------------//
            
            //
            
            
            
            // 三级转换:NSArray中也是字典,把数组中的字典转换成模型.
            
            // 判断值是否是数组
            
            if ([value isKindOfClass:[NSArray class]]) {
                
                // 判断对应类有没有实现字典数组转模型数组的协议
                
                // arrayContainModelClass 提供一个协议,只要遵守这个协议的类,都能把数组中的字典转模型
                
                if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
                    
                    
                    
                    // 转换成id类型,就能调用任何对象的方法
                    
                    id idSelf = self;
                    
                    
                    
                    // 获取数组中字典对应的模型
                    
                    NSString *type =  [idSelf arrayContainModelClass][key];
                    
                    
                    // 生成模型
                    
                    Class classModel = NSClassFromString(type);
                    
                    NSMutableArray *arrM = [NSMutableArray array];
                    
                    // 遍历字典数组,生成模型数组
                    
                    for (NSDictionary *dict in value) {
                        
                        // 字典转模型
                        
                        id model =  [classModel modelWithDict3:dict];
                        
                        [arrM addObject:model];
                        
                    }
                    
                    
                    
                    // 把模型数组赋值给value
                    
                    value = arrM;
                    
                    
                    
                }
                
            }
            
            
            
            // 如果模型属性数量大于字典键值对数理,模型属性会被赋值为nil,而报错
            
            if (value) {
                
                // 给模型中属性赋值
                
                [objc setValue:value forKey:key];
                
            }
            
        }
        
        return objc;
        
    }
    
    

    runtime字典转模型-->数组中装着模型 打印输出
    总结:我们既然能获取到属性类型,那就可以拦截到模型的那个数组属性,进而对数组中每个模型遍历并字典转模型,但是我们不知道数组中的模型都是什么类型,我们可以声明一个方法,该方法目的不是让其调用,而是让其实现并返回模型的类型。

    6.实现NSCoding的自动归档和解档
    在开发项目的过程中,我们经常会用到需要对一个模型进行本地存储,这样就好对对模型惊喜解档和归档,也就是序列化。一般我们是这样写的
    eg:
    .h

    #import <Foundation/Foundation.h>
    @class TestModel;
    @protocol PersonDelegate <NSObject>
    - (void)test;
    @end
    
    @interface Person : NSObject
    @property (nonatomic,strong)NSString *name;
    @property (nonatomic,assign)NSInteger age;
    @property (nonatomic,assign)BOOL sex;
    @property (nonatomic,strong)TestModel *model;
    @end
    
    

    .m

    
    #import "Person.h"
    @interface Person()<NSCoding>
    
    @end
    @implementation Person
    
    - (void)encodeWithCoder:(NSCoder *)aCoder
    {
        [aCoder encodeObject:_name forKey:@"name"];
        [aCoder encodeInteger:_age forKey:@"age"];
        [aCoder encodeBool:_sex forKey:@"sex"];
        [aCoder encodeObject:_model forKey:@"model"];
    }
    
    - (instancetype)initWithCoder:(NSCoder *)aDecoder
    {
        self = [super init];
        if (self)
        {
            self.name  = [aDecoder decodeObjectForKey:@"name"];
            self.age   = [aDecoder decodeIntegerForKey:@"age"];
            self.sex   = [aDecoder decodeBoolForKey:@"sex"];
            self.model = [aDecoder decodeObjectForKey:@"model"];
        }
        
        return self;
        
    }
    @end
    

    如果这里有100个属性,或者更多,那么再手写就太麻烦了。
    如果使用runtime,可以很好的解决这个问题。
    代码如下:

    - (void)encodeWithCoder:(NSCoder *)encoder
    
    {
        
        unsigned int count = 0;
        
        Ivar *ivars = class_copyIvarList([self class], &count);
        
        
        
        for (int i = 0; i<count; i++)
        {
             // 取出i位置对应的成员变量
             
             Ivar ivar = ivars[i];
             
             // 查看成员变量
             
             const char *name = ivar_getName(ivar);
             
             // 归档
             
             NSString *key = [NSString stringWithUTF8String:name];
             
             id value = [self valueForKey:key];
             
             [encoder encodeObject:value forKey:key];
             
        }
             
             free(ivars);
             
    }
             
             
             
    - (id)initWithCoder:(NSCoder *)decoder
    {
            
            if (self = [super init])
            {
                
                unsigned int count = 0;
                
                Ivar *ivars = class_copyIvarList([self class], &count);
                
                for (int i = 0; i<count;i++)
                {
                     
                     // 取出i位置对应的成员变量
                     
                     Ivar ivar = ivars[i];
                     
                     // 查看成员变量
                     
                     const char *name = ivar_getName(ivar);
                     
                     // 归档
                     
                     NSString *key = [NSString stringWithUTF8String:name];
                     
                     id value = [decoder decodeObjectForKey:key];
                     
                     // 设置到成员变量身上
                     
                     [self setValue:value forKey:key];
         
                 }
                     
                    free(ivars);
                     
                }
                     
                return self;
                     
      }
    

    也可定义两个宏 这样就可以在各处调用了
    eg:

    #define encodeRuntime(A) \
    - (void)encodeWithCoder:(NSCoder *)aCoder\
    {\
    unsigned int count = 0;\
    Ivar *ivars = class_copyIvarList([self class], &count);\
    for (int i = 0; i<count; i++)\
    {\
        Ivar ivar = ivars[i];\
        const char *name = ivar_getName(ivar);\
        NSString *key = [NSString stringWithUTF8String:name];\
        id value = [self valueForKey:key];\
        [encoder encodeObject:value forKey:key];\
    }\
    free(ivars);\
    }
         
         
    #define initCoderRuntime(A) \
    - (id)initWithCoder:(NSCoder *)aDecoder\
    {\
    if (self = [super init]){\
             unsigned int count = 0;\
             Ivar *ivars = class_copyIvarList([A class], &count);\
             for (int i = 0; i<count;i++)\
             {\
                 Ivar ivar = ivars[i];\
                 const char *name = ivar_getName(ivar);\
                 NSString *key = [NSString stringWithUTF8String:name];\
                 id value = [decoder decodeObjectForKey:key];\
                 [self setValue:value forKey:key];\
             }\
             free(ivars);\
         }\
    return self;\
    }
    

    在.m方法里直接调用就可以了

    #import "Person.h"
    #import <objc/runtime.h>
    @interface Person()<NSCoding>
    @end
    @implementation Person
    
    encodeRuntime(self)
    
    initCoderRuntime(self)
    
    @end
    

    7.获取类的实例方法列表 getter,setter,对象方法等,不能获取类方法

    + (NSArray *)getInstaceMethodListWithClass:(Class)class
    {
        unsigned int count = 0;
        Method *methodList = class_copyMethodList(class, &count);
        NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
        for (unsigned int i = 0; i<count; i++)
        {
            Method method = methodList[i];
            SEL methodName = method_getName(method);
            [mutableList addObject:NSStringFromSelector(methodName)];
        }
        free(methodList);
        return [NSArray arrayWithArray:mutableList];
    }
    
    

    8.获取协议列表

    + (NSArray *)getProtocolListWithClass:(Class)class
    {
        unsigned int count = 0;
        __unsafe_unretained Protocol **protocolList = class_copyProtocolList(class, &count);
        NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
        for (unsigned int i = 0; i<count; i++)
        {
            Protocol *protocol = protocolList[i];
            const char *protocolName = protocol_getName(protocol);
            [mutableList addObject:[NSString stringWithUTF8String:protocolName]];
            
        }
        return [NSArray arrayWithArray:mutableList];
        return nil;
    }
    
    

    Demo地址

    相关文章

      网友评论

          本文标题:iOS运行时(Runtime) 常用总结

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