Runtime系列9之-Runtime的应用(二)

RunTime获取成员变量/属性/方法/协议列表

  1. 代码如下:

      unsigned int count = 0;
      //获取属性列表
     + (void)getProperties
     {
         unsigned int count = 0;
         // 拷贝出所有的成员变量属性列表
         objc_property_t *properties = class_copyPropertyList([UITextField class], &count);
         for (int i = 0; i<count; i++) {
             // 取出属性
             objc_property_t property = properties[i];
             // 打印属性名字
             NSLog(@"%s   <---->   %s", property_getName(property), property_getAttributes(property));
         }
         free(properties);
     }
     //获取方法列表
     + (void)getMethods
     {
         unsigned int count = 0;
         // 拷贝出所有的方法列表
         Method *methods = class_copyMethodList([UITextField class], &count);
         for (int i = 0; i<count; i++) {
             // 取出成员变量
             Method method = methods[i];
             // 打印该类的方法
             NSLog(@"%@ %p",  NSStringFromSelector(method_getName(method)), method_getImplementation(method));
               
         }
         // 释放
         free(methods);
     }
     //获取成员变量列表
     + (void)getIvars
     {
         unsigned int count = 0;
         // 拷贝出所有的成员变量列表
         Ivar *ivars = class_copyIvarList([UITextField class], &count);
         for (int i = 0; i<count; i++) {
             // 取出成员变量
             //        Ivar ivar = *(ivars + i);
             Ivar ivar = ivars[i];
             // 打印成员变量名字
             NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
         }
         // 释放
         free(ivars);
     }
     //获取协议列表
     + (void)getProtocols
     {
         //获取协议列表
         __unsafe_unretained Protocol **protocolList = class_copyProtocolList([UITextField class], &count);
         for (unsigned int i = 0; i<count; i++) {
             Protocol *myProtocal = protocolList[i];
             const char *protocolName = protocol_getName(myProtocal);
             NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
               
             /*
              //oc字符串与C语言字符串之间的转换
              NSString---->char *
              [@"dfafds" UTF8String];
              char *---->NSString
              [NSString stringWithUTF8String:protocolName]
              */
         }
     }
    

Runtime字典转模型

  1. 为了方便以后重用,这里通过给NSObject添加分类,声明并实现使用RunTime字典转模型的类方法。

     + (instancetype)modelWithDict:(NSDictionary *)dict
    
  2. 首先来看一下KVC字典转模型和RunTime字典转模型的区别:
    1. KVC: KVC字典转模型实现原理是遍历字典中所有Key,然后去模型中查找相对应的属性名,要求属性名与Key必须一一对应,字典中所有key必须在模型中存在。
    2. RunTime: RunTime字典转模型实现原理是遍历模型中的所有属性名,然后去字典查找相对应的Key,也就是以模型为准,模型中有哪些属性,就去字典中找那些属性。
    3. RunTime字典转模型的优点: 当服务器返回的数据过多,而我们只使用其中很少一部分时,没有用的属性就没有必要定义成属性浪费不必要的资源。只保存最有用的属性即可。
  3. RunTime字典转模型过程
    1. 首先需要了解,属性定义在类里面,那么类里面就有一个属性列表,属性列表以数组的形式存在,根据属性列表就可以获得类里面的所有属性名,所以遍历属性列表,也就可以遍历模型中的所有属性名。
    2. 所以RunTime字典转模型过程就很清晰了。
      1. 创建模型对象

         id objc = [[self alloc] init];
        
      2. 使用class_copyIvarList方法拷贝成员属性列表

         unsigned int count = 0;
         Ivar *ivarList = class_copyIvarList(self, &count);
        
        1. 参数一:__unsafe_unretained Class cls : 获取哪个类的成员属性列表。这里是self,因为谁调用分类中类方法,谁就是self。
        2. 参数二:unsigned int *outCount : 无符号int型指针,这里创建unsigned int型count,&count就是他的地址,保证在方法中可以拿到count的地址为count赋值。传出来的值为成员属性总数。
        3. 返回值:Ivar * : 返回的是一个Ivar类型的指针 。指针默认指向的是数组的第0个元素,指针+1会向高地址移动一个Ivar单位的字节,也就是指向第一个元素。Ivar表示成员属性。
      3. 遍历成员属性列表,获得属性列表

         for (int i = 0 ; i < count; i++) {
         // 获取成员属性
         Ivar ivar = ivarList[i];
          }
        
      4. 使用ivar_getName(ivar)获得成员属性名,因为成员属性名返回的是C语言字符串,将其转化成OC字符串,通过ivar_getTypeEncoding(ivar)可以获得成员属性类型。

        NSString *propertyName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        
      5. 因为获得的是成员属性名,是带_的成员属性,所以需要将下划线去掉,获得属性名,也就是字典的key。

         // 获取key
         NSString *key = [propertyName substringFromIndex:1];
        
      6. 获取字典中key对应的Value。

         // 获取字典的value
         id value = dict[key];
        
      7. 给模型属性赋值,并将模型返回

         if (value) {
         // KVC赋值:不能传空
         [objc setValue:value forKey:key];
         }
          return objc;
        
  4. 至此已成功将字典转为模型。
    1. 代码如下:
     #import <Foundation/Foundation.h>
        
     @interface NSObject (Extension)
     + (instancetype)modelWithDict:(NSDictionary *)dict;
     @end
        
     #import "NSObject+Extension.h"
     #import <objc/runtime.h>
     @implementation NSObject (Extension)
     +(instancetype)modelWithDict:(NSDictionary *)dict{
         id objc = [[self alloc] init];
         unsigned int count = 0;
         Ivar *ivarList = class_copyIvarList(self, &count);
         for (int i = 0 ; i < count; i++) {
             // 获取成员属性
             Ivar ivar = ivarList[i];
             NSString *propertyName = [NSString stringWithUTF8String:ivar_getName(ivar)];
             NSString *key = [propertyName substringFromIndex:1];
             id value = dict[key];
             if (value) {
                 // KVC赋值:不能传空
                 [objc setValue:value forKey:key];
             }
         }
         return objc;
     }
     @end
    

Runtime字典转模型二级转换

  1. 在开发过程中经常用到模型嵌套,也就是模型中还有一个模型,这里尝试用RunTime进行模型的二级转换,实现思路其实比较简单清晰。
  2. 首先获得一级模型中的成员属性的类型

     // 成员属性类型
     NSString *propertyType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
    
  3. 判断当一级字典中的value是字典,并且一级模型中的成员属性类型不是NSDictionary的时候才需要进行二级转化。
    1. 首先一级字典中的value是字典才进行转化是必须的,因为我们通常将字典转化为模型
    2. 其次,一级模型的成员属性类型不是系统类,说明成员属性是我们自定义的类,也就是要转化的二级模型。而当一级模型的成员属性类型就是NSDictionary的话就表明,我们本就想让成员属性是一个字典,不需要进行模型的转换。
      //拿到字典中每个key对应的值
      id value = dict[key];
      //判断value是不是NSDictionary类,而且判断一级模型的对应属性不是系统自带类
      if ([value isKindOfClass:[NSDictionary class]] && ![propertyType containsString:@"NS"])
      {
      // 进行二级转换。
      }
    
  4. 获取要转换的模型类型,这里需要对propertyType成员属性类型做一些处理,因为propertyType返回给我们成员属性类型的是@\"Mode\",我们需要对他进行截取为Mode。这里需要注意的是\只是转义符,不占位。

     // @\"Mode\"去掉前面的@\"
      NSRange range = [propertyType rangeOfString:@"\""];
      propertyType = [propertyType substringFromIndex:range.location + range.length];
      // Mode\"去掉后面的\"
      range = [propertyType rangeOfString:@"\""];
      propertyType = [propertyType substringToIndex:range.location];
    
  5. 获取需要转换类的类对象,将字符串转化为类名。

     Class modelClass =  NSClassFromString(propertyType);
    
  6. 判断如果类名不为空则调用分类的modelWithDict方法,传value字典,进行二级模型转换,返回二级模型在赋值给一级模型key(此时key对应的类就是二级模型的类)对应的value。

    if (modelClass) {
     value =  [modelClass modelWithDict:value];
     }
    
  7. 总结:
    1. 我们通过判断一级字典key对应的value是字典并且该key在一级模型中对应的属性类为自定义类
    2. 然后将value字典转化为二级模型返回,并重新赋值给(id类)value
    3. 最后给一级模型中相对应的key赋值模型value即可完成二级字典对模型的转换。
  8. 最后附上二级转换的完整方法:
+ (instancetype)modelWithDict:(NSDictionary *)dict{
    // 1.创建对应类的对象
    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 *propertyName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        // 获取key
        NSString *key = [propertyName substringFromIndex:1];
        // 获取字典的value key:属性名 value:字典的值
        id value = dict[key];
        // 获取成员属性类型
        NSString *propertyType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
        // 二级转换
        // value值是字典并且成员属性的类型不是字典,才需要转换成模型
        if ([value isKindOfClass:[NSDictionary class]] && ![propertyType containsString:@"NS"]) {
            // 进行二级转换
            // 获取二级模型类型进行字符串截取,转换为类名
            NSRange range = [propertyType rangeOfString:@"\""];
            propertyType = [propertyType substringFromIndex:range.location + range.length];
            range = [propertyType rangeOfString:@"\""];
            propertyType = [propertyType substringToIndex:range.location];
            // 获取需要转换类的类对象
            Class modelClass =  NSClassFromString(propertyType);
            // 如果类名不为空则进行二级转换
            if (modelClass) {
                // 返回二级模型赋值给value
                value =  [modelClass modelWithDict:value];
            }
        }
        if (value) {
            // KVC赋值:不能传空
            [objc setValue:value forKey:key];
        }
    }
    // 返回模型
    return objc;
}
Table of Contents