OC和Swift中的Runtime

作者: John_LS | 来源:发表于2016-04-19 16:12 被阅读832次

    转载

    运行时机制

    runtime是一套比较底层的纯C语言的API, 属于C语言库, 包含了很多底层的C语言API。
    在我们平时编写的iOS代码中, 最终都是转成了runtime的C语言代码。

    所谓运行时,也就是在编译时是不存在的,只是在运行过程中才去确定对象的类型、方法等。利用Runtime机制可以在程序运行时动态修改类、对象中的所有属性、方法等。

    还记得我们在网络请求数据处理时,调用了-setValuesForKeysWithDictionary:方法来设置模型的值。这里什么原理呢?为什么能这么做?其实就是通过Runtime机制来完成的,内部会遍历模型类的所有属性名,然后设置与key对应的属性名的值。

    我们在使用运行时的地方,都需要包含头文件:#import <objc/runtime.h>。如果是Swift就不需要包含头文件,就可以直接使用了。

    runtime基础

    1. 获取对象所有属性名

    利用运行时获取对象的所有属性名是可以的,但是变量名获取就得用另外的方法了。我们可以通过class_copyPropertyList方法获取所有的属性名称。

    • 第一个参数:类
    • 第二个参数:存放属性个数的地址

    下面我们通过一个Person类来学习,这里的方法没有写成扩展,只是为了简化,将获取属性名的方法直接作为类的实例方法:

    Objective-C版
    @interface Person : NSObject{
        NSString *_variableString;
    }
    
    // 默认会是什么呢?
    @property (nonatomic, copy) NSString *name;
    
    // 默认是strong类型
    @property (nonatomic, strong) NSMutableArray *array;
    
    // 获取所有的属性名
    - (NSArray *)allProperties;
    @end
    

    下面主要是写如何获取类的所有属性名的方法。注意,这里的objc_property_t是一个结构体指针objc_property *,因此我们声明的properties就是二维指针。在使用完成后,我们一定要记得释放内存,否则会造成内存泄露。这里是使用的是C语言的API,因此我们也需要使用C语言的释放内存的方法free。

    @implementation Person
    
    typedef struct objc_property *objc_property_t;
    
    - (NSArray *)allProperties {
        unsigned int count;
        
        // 获取类的所有属性
        // 如果没有属性,则count为0,properties为nil
        objc_property_t *properties = class_copyPropertyList([self class], &count);
        NSMutableArray *propertiesArray = [NSMutableArray arrayWithCapacity:count];
        
        for (NSUInteger i = 0; i < count; i++) {
            // 获取属性名称
            const char *propertyName = property_getName(properties[i]);
            NSString *name = [NSString stringWithUTF8String:propertyName];
            
            [propertiesArray addObject:name];
        }
        
        // 注意,这里properties是一个数组指针,是C的语法,
        // 我们需要使用free函数来释放内存,否则会造成内存泄露
        free(properties);
        
        return propertiesArray;
    }
    
    

    来测试一下,我们的方法是否正确获取到了呢?看下面的打印结果就明白了吧

    Person *p = [[Person alloc] init];
        p.name = @"Lili";
        
        size_t size = class_getInstanceSize(p.class);
        NSLog(@"size=%ld", size);
        
        for (NSString *propertyName in p.allProperties) {
            NSLog(@"%@", propertyName);
        }
        // 打印结果:
        // 2016-04-19 11:37:24.589 LSRuntimeOCDemo[1554:108843] size=32
        // 2016-04-19 11:37:24.590 LSRuntimeOCDemo[1554:108843] name
        // 2016-04-19 11:37:24.590 LSRuntimeOCDemo[1554:108843] array
    
    
    Swift版

    对于Swift版,使用C语言的指针就不容易了,因为Swift希望尽可能减少C语言的指针的直接使用,因此在Swift中已经提供了相应的结构体封装了C语言的指针。但是看起来好复杂,使用起来好麻烦。看看Swift版的获取类的属性名称如何做:

    class Person: NSObject {
        var name: String = ""
        var hasBMW = false
        
        override init() {
            super.init()
        }
        
        func allProperties() ->[String] {
            // 这个类型可以使用CUnsignedInt,对应Swift中的UInt32
            var count : UInt32 = 0
            
            let properties = class_copyPropertyList(Person.self, &count)
            
            ///定义一个元素为字符串的数组
            var propertyNames: [String] = []
            
            // Swift中类型是严格检查的,必须转换成同一类型
            for var i  in 0..<Int(count) {
                // UnsafeMutablePointer<objc_property_t>是可变指针,因此properties就是类似数组一样,可以通过下标获取
                let property = properties[i]
                let name = property_getName(property)
                
                
                // 这里还得转换成字符串
                let strName = String.fromCString(name)
                propertyNames.append(strName!)
            }
            // 不要忘记释放内存,否则C语言的指针很容易成野指针的
            free(properties)
            
            
            return propertyNames
        }
    }
    

    关于Swift中如何C语言的指针问题,这里不细说,如果需要了解,请查阅相关文章。
    测试一下是否获取正确:

            let p = Person()
            p.name = "Lili"
            
            // 打印结果:["name", "hasBMW"],说明成功
            print( p.allProperties() )
    

    2.获取对象的所有属性名和属性值

    对于获取对象的所有属性名,在上面的-allProperties方法已经可以拿到了,但是并没有处理获取属性值,下面的方法就是可以获取属性名和属性值,将属性名作为key,属性值作为value。

    Object-C版
    - (NSDictionary *)allPropertyNamesAndValues {
        NSMutableDictionary *resultDict = [NSMutableDictionary dictionary];
        
        unsigned int outCount;
        objc_property_t *properties = class_copyPropertyList([self class], &outCount);
        
        for (int i = 0; i < outCount; i++) {
            objc_property_t property = properties[i];
            const char *name = property_getName(property);
            
            // 得到属性名
            NSString *propertyName = [NSString stringWithUTF8String:name];
            
            // 获取属性值
            id propertyValue = [self valueForKey:propertyName];
            
            if (propertyValue && propertyValue != nil) {
                ///如果值为空则不添加进字典
                [resultDict setObject:propertyValue forKey:propertyName];
            }
        }
        
        // 记得释放
        free(properties);
        
        return resultDict;
    }
    

    测试一下

    // 此方法返回的只有属性值不为空的属性
        NSDictionary *dict = p.allPropertyNamesAndValues;
        for (NSString *propertyName in dict.allKeys) {
            NSLog(@"propertyName: %@ propertyValue: %@",
                  propertyName,
                  dict[propertyName]);
        }
    输出结果:
    2016-04-19 12:30:49.367 LSRuntimeOCDemo[1983:145600] propertyName: name propertyValue: Lili
    
    Siwft版
    func allPropertyNamesAndValues() ->[String: AnyObject] {
            var count: UInt32 = 0
            let properties = class_copyPropertyList(Person.self, &count)
            
            var resultDict: [String: AnyObject] = [:]
            for var i in 0..<Int(count) {
                let property = properties[i]
                
                // 取得属性名
                let name = property_getName(property)
                if let propertyName = String.fromCString(name) {
                    // 取得属性值
                    if let propertyValue = self.valueForKey(propertyName) {
                        ///属性不为空的添加在字典中
                        resultDict[propertyName] = propertyValue
                    }
                }
            }
            
            return resultDict
        }
    
    

    测试一下:

    let dict = p.allPropertyNamesAndValues()
            for (propertyName, propertyValue) in dict {
                print("propertyName: (\(propertyName)), propertyValue: (\(propertyValue))")
            }
    

    打印结果:

    propertyName: (hasBMW), propertyValue: (0)
    propertyName: (name), propertyValue: (Lili)
    

    3. 获取对象的所有方法名

    通过class_copyMethodList方法就可以获取所有的方法。

    • 第一个参数:类
    • 第二个参数:存放方法个数的地址
    Object-C版
    - (void)allMethods {
        unsigned int outCount = 0;
        Method *methods = class_copyMethodList([self class], &outCount);
        
        for (int i = 0; i < outCount; ++i) {
            Method method = methods[i];
            
            // 获取方法名称,但是类型是一个SEL选择器类型
            SEL methodSEL = method_getName(method);
            // 需要获取C字符串
            const char *name = sel_getName(methodSEL);
            // 将方法名转换成OC字符串
            NSString *methodName = [NSString stringWithUTF8String:name];
            
            // 获取方法的参数列表
            int arguments = method_getNumberOfArguments(method);
            NSLog(@"方法名:%@, 参数个数:%d", methodName, arguments);
        }
        
        // 记得释放
        free(methods);
    }
    

    测试

    ///获取所有的方法名
        [p allMethods];
    

    打印结果:

    2016-04-19 13:33:49.999 LSRuntimeOCDemo[2174:175322] 方法名:allProperties, 参数个数:2
    2016-04-19 13:33:50.000 LSRuntimeOCDemo[2174:175322] 方法名:allPropertyNamesAndValues, 参数个数:2
    2016-04-19 13:33:50.000 LSRuntimeOCDemo[2174:175322] 方法名:allMethods, 参数个数:2
    2016-04-19 13:33:50.000 LSRuntimeOCDemo[2174:175322] 方法名:setArray:, 参数个数:3
    2016-04-19 13:33:50.000 LSRuntimeOCDemo[2174:175322] 方法名:.cxx_destruct, 参数个数:2
    2016-04-19 13:33:50.000 LSRuntimeOCDemo[2174:175322] 方法名:name, 参数个数:2
    2016-04-19 13:33:50.000 LSRuntimeOCDemo[2174:175322] 方法名:array, 参数个数:2
    2016-04-19 13:33:50.000 LSRuntimeOCDemo[2174:175322] 方法名:setName:, 参数个数:3
    
    Swift版
    func allMethods() {
            var count : UInt32 = 0
            let methods = class_copyMethodList(Person.self,&count)
            
            for var i in 0..<Int(count) {
                let method = methods[i]
                let sel = method_getName(method)
                // 获取方法的参数列表
                let argument = method_getNumberOfArguments(method)
                 print("name: \(sel), arguemtns: \(argument)")
            }
        }
    

    测试一下:

    ///获取所有方法名
            p.allMethods()
    

    打印结果:

    name: hasBMW, arguemtns: 2
    name: setHasBMW:, arguemtns: 3
    name: allProperties, arguemtns: 2
    name: allPropertyNamesAndValues, arguemtns: 2
    name: allMethods, arguemtns: 2
    name: name, arguemtns: 2
    name: .cxx_destruct, arguemtns: 0
    name: init, arguemtns: 2
    name: setName:, arguemtns: 3
    

    4. 获取对象的成员变量名称

    要获取对象的成员变量,可以通过class_copyIvarList方法来获取,通过ivar_getName来获取成员变量的名称。对于属性,会自动生成一个成员变量。使用方法与前面类似。

    • 第一个参数:类
    • 第二个参数:存放变量个数的地址
    Object-C版
    - (NSArray *)allMemberVariables {
        unsigned int count = 0;
        ///Ivar 变量
        ///获取成员变量的数组 (指针)
        Ivar *ivars = class_copyIvarList([self class], &count);
        
        NSMutableArray *results = [[NSMutableArray alloc] init];
        for (NSUInteger i = 0; i < count; ++i) {
            ///获取每个成员变量
            Ivar variable = ivars[i];
            ///获取成员变量的字符名称
            const char *name = ivar_getName(variable);
            ///将名称转为NSString
            NSString *varName = [NSString stringWithUTF8String:name];
            
            [results addObject:varName];
        }
        free(ivars);
        return results;
    }
    

    测试:

    NSLog(@"%@",[p allMemberVariables]);
    

    打印结果:

    2016-04-19 14:09:50.781 LSRuntimeOCDemo[2555:199104] (
        "_variableString",
        "_name",
        "_array"
    )
    
    Swift版

    Swift的成员变量名与属性名是一样的,不会生成下划线的成员变量名,这一点与Oc是有区别的。

    func allMemberVariables() ->[String] {
            var count:UInt32 = 0
            let ivars = class_copyIvarList(Person.self, &count)
            
            var result: [String] = []
            for var i = 0; i < Int(count); ++i {
                let ivar = ivars[i]
                
                let name = ivar_getName(ivar)
                
                if let varName = String.fromCString(name) {
                    result.append(varName)
                }
            }
            
            return result
        }
    

    测试:

    ///获取成员变量
      print(p.allMemberVariables())
    

    打印结果:说明Swift的属性不会自动加下划线,属性名就是变量名:

    ["name", "hasBMW"]
    

    5. 运行时发消息

    Object-C版
    Person *p = [[Person alloc] init];
    p.name = @"Lili";
    objc_msgSend(p, @selector(allMethods));
    

    这样就相当于手动调用[p allMethods];。但是编译器会抱错,问题提示期望的参数为0,但是实际上有两个参数。解决办法是,关闭严格检查:

    如果,此时报错
    objc_msgSend()报错Too many arguments to function call ,expected 0,have2
    那么,请Build Setting-->搜索 Enable Strict Checking of objc_msgSend Calls 改为 NO

    屏幕快照 2016-04-19 下午2.44.35.png
    Swift版

    抱歉,Swift中没有此类方法。

    6. Category扩展”属性”

    iOS的category是不能扩展存储属性的,但是我们可以通过运行时关联来扩展“属性”。

    Object-C 版

    假设扩展下面的“属性”:

    // 由于扩展不能扩展属性,因此我们这里在实现文件中需要利用运行时实现。
    #import <objc/runtime.h>
    
    typedef void(^LSCallBack)();
    @interface NSObject (Property)
    
    @property (nonatomic, copy) LSCallBack callback;
    @end
    
    

    在实现文件中,我们用一个静态变量作为key:

    const void *s_LSCallbackKey = "s_LSCallbackKey";
    
    - (void)setCallback:(LSCallBack)callback {
    // 第一个参数:给哪个对象添加关联
    // 第二个参数:关联的key,通过这个key获取
    // 第三个参数:关联的value
    // 第四个参数:关联的策略
      objc_setAssociatedObject(self, s_LSCallbackKey, callback, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    
    - (HYBCallBack)callback {
      return objc_getAssociatedObject(self, s_LSCallbackKey);
    }
    

    测试:

    p.callback=^(){
            NSLog(@"aaaa");
        };
        p.callback();
    

    其实就是通过objc_getAssociatedObject取得关联的值,通过objc_setAssociatedObject设置关联。

    Swift版

    Swift版的要想扩展闭包,就比OC版的要复杂得多了。这里只是例子,写了一个简单的存储属性扩展。

    let s_LSFullnameKey = "s_LSFullnameKey"
    
    extension Person {
        var fullName: String? {
            get { return objc_getAssociatedObject(self, s_LSFullnameKey) as? String }
            set {
                // 第一个参数:给哪个对象添加关联
                // 第二个参数:关联的key,通过这个key获取
                // 第三个参数:关联的value
                // 第四个参数:关联的策略
                objc_setAssociatedObject(self, s_LSFullnameKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
            }
        }
    }
    

    总结

    在开发中,我们比较常用的是使用关联属性的方式来扩展我们的“属性”,以便在开发中简单代码。我们在开发中使用关联属性扩展所有响应事件、将代理转换成block版等。比如,我们可以将所有继承于UIControl的控件,都拥有block版的点击响应,那么我们就可以给UIControl扩展一个TouchUp、TouchDown、TouchOut的block等。
    对于动态获取属性的名称、属性值使用较多的地方一般是在使用第三方库中,比如MJExtension等。这些三方库都是通过这种方式将Model转换成字典,或者将字典转换成Model

    另外

    1.交换方法

    开发使用场景:系统自带的方法功能不够,给系统自带的方法扩展一些功能,并且保持原有的功能。

    // 需求:给imageNamed方法提供功能,每次加载图片就判断下图片是否加载成功。
    // 步骤一:先搞个分类,定义一个能加载图片并且能打印的方法+ (instancetype)imageWithName:(NSString *)name;
    // 步骤二:交换imageNamed和imageWithName的实现,就能调用imageWithName,间接调用imageWithName的实现。

    @implementation UIImage (image)
    // 加载分类到内存的时候调用
    
    +(void)load
    {
        // 交换方法
        
        // 获取imageWithName方法地址
        Method imageWithName = class_getClassMethod(self, @selector(imageWithName:));
        
        // 获取imageWithName方法地址
        Method imageName = class_getClassMethod(self, @selector(imageNamed:));
        
        // 交换方法地址,相当于交换实现方式
        method_exchangeImplementations(imageWithName, imageName);
        
    }
    
    // 不能在分类中重写系统方法imageNamed,因为会把系统的功能给覆盖掉,而且分类中不能调用super.
    
    // 既能加载图片又能打印
    
    +(instancetype)imageWithName:(NSString *)name
    {
        
        // 这里调用imageWithName,相当于调用imageName
        UIImage *image = [self imageWithName:name];
        
        if (image == nil) {
            
            NSLog(@"加载空的图片");
        }
        
        return image;
    }
    
    

    测试:

    UIImage *image = [UIImage imageNamed:@"123"];
    

    打印结果:

    2016-04-19 16:26:45.317 LSRuntimeOCDemo[3394:299037] 加载空的图片
    

    2.动态添加方法

    开发使用场景:如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。
    经典面试题:有没有使用performSelector,其实主要想问你有没有动态添加过方法。
    简单使用

    例如:
    Person *p = [[Person alloc] init];
    // 默认person,没有实现eat方法,可以通过performSelector调用,但是会报错。

    // 动态添加方法就不会报错
    [p performSelector:@selector(eat)];

    实现:

    #import "Person.h"
    
    @implementation Person
    
    // void(*)()
    // 默认方法都有两个隐式参数,
    void eat(id self,SEL sel)
    {
        NSLog(@"%@ %@",self,NSStringFromSelector(sel));
    }
    
    // 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.
    // 刚好可以用来判断,未实现的方法是不是我们想要动态添加的方法
    
    +(BOOL)resolveInstanceMethod:(SEL)sel
    {
        
        if (sel == @selector(eat)) {
            
            // 动态添加eat方法
            
            // 第一个参数:给哪个类添加方法
            // 第二个参数:添加方法的方法编号
            // 第三个参数:添加方法的函数实现(函数地址)
            // 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
            class_addMethod(self, @selector(eat), eat, "v@:");
        }
        
        return [super resolveInstanceMethod:sel];
    }
    

    测试:

    Person *p = [[Person alloc] init];
    // 默认person,没有实现eat方法,可以通过performSelector调用,但是会报错。
    // 动态添加方法就不会报错
    [p performSelector:@selector(eat)];
    

    打印:

    2016-04-19 16:36:50.275 LSRuntimeOCDemo[3429:304239] <Person: 0x7f8620d9dd00> eat
    

    3.字典转模型

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

    @implementation NSObject (Log)
    +(void)resolveDict:(NSDictionary *)dict{
        // 拼接属性字符串代码
        NSMutableString *strM = [NSMutableString string];
        // 1.遍历字典,把字典中的所有key取出来,生成对应的属性代码
        [dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
           // 类型经常变,抽出来
            NSString *type;
            if ([obj isKindOfClass:NSClassFromString(@"__NSCFString")]) {
                type = @"NSString";
            }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFArray")]){
                type = @"NSArray";
            }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFNumber")]){
                type = @"int";
            }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFDictionary")]){ type = @"NSDictionary";
            }
        
           // 属性字符串
            NSString *str;
            if ([type containsString:@"NS"]) {
                str = [NSString stringWithFormat:@"@property (nonatomic, strong) %@ *%@;",type,key];
            }else{
                str = [NSString stringWithFormat:@"@property (nonatomic, assign) %@ %@;",type,key];
            }
            // 每生成属性字符串,就自动换行。
            [strM appendFormat:@"\n%@\n",str];
        }];
        // 把拼接好的字符串打印出来,就好了。
        NSLog(@"%@",strM);
        
    }
    @end
    
    字典转模型的方式一:KVC
    @implementation Status
    +(instancetype)statusWithDict:(NSDictionary *)dict
    {
        Status *status = [[self alloc] init];
        
        [status setValuesForKeysWithDictionary:dict];
        
        return status;
        
    }
    @end
    

    KVC字典转模型弊端:必须保证,模型中的属性和字典中的key一一对应。
    如果不一致,就会调用[<Status 0x7fa74b545d60> setValue:forUndefinedKey:] 报key找不到的错。
    分析:模型中的属性和字典的key不一一对应,系统就会调用setValue:forUndefinedKey:报错。
    解决:重写对象的setValue:forUndefinedKey:,把系统的方法覆盖, 就能继续使用KVC,字典转模型了。

    -(void)setValue:(id)value forUndefinedKey:(NSString *)key
    {
    }
    
    字典转模型的方式二:Runtime

    思路:利用运行时,遍历模型中所有属性,根据模型的属性名,去字典中查找key,取出对应的值,给模型的属性赋值。
    步骤:提供一个NSObject分类,专门字典转模型,以后所有模型都可以通过这个分类转。

    #import "NSObject+Model.h"
    
    @implementation NSObject (Model)
    +(instancetype)modelWithDict:(NSDictionary *)dict
    {
        // 思路:遍历模型中所有属性-》使用运行时
        
        // 0.创建对应的对象
        id objc = [[self alloc] init];
        
        // 1.利用runtime给对象中的成员属性赋值
        
        // class_copyIvarList:获取类中的所有成员属性
        // Ivar:成员属性的意思
        // 第一个参数:表示获取哪个类中的成员属性
        // 第二个参数:表示这个类有多少成员属性,传入一个Int变量地址,会自动给这个变量赋值
        // 返回值Ivar :指的是一个ivar数组,会把所有成员属性放在一个数组中,通过返回的数组就能全部获取到。
        // 类似下面这种写法
        
    //    Ivar ivar;
    //    Ivar ivar1;
    //    Ivar ivar2;
    //    // 定义一个ivar的数组a
    //    Ivar a[] = {ivar,ivar1,ivar2};
    //    
    //    // 用一个Ivar 指针指向数组第一个元素
    //    Ivar ivarList = a;
    //    
    //    // 根据指针访问数组第一个元素
    //    ivarList[0];
        
        
        unsigned int count;
        
        // 获取类中的所有成员属性
        Ivar *ivarList = class_copyIvarList(self, &count);
        
        for (int i = 0; i < count; i++) {
            
            // 根据角标,从数组取出对应的成员属性
            Ivar ivar = ivarList[i];
            
            // 获取成员属性名
            NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
            
            // 处理成员属性名->字典中的key
            // 从第一个角标开始截取
            NSString *key = [name substringFromIndex:1];
            
            // 根据成员属性名去字典中查找对应的value
            id value = dict[key];
            
            // 二级转换:如果字典中还有字典,也需要把对应的字典转换成模型
            // 判断下value是否是字典
            if ([value isKindOfClass:[NSDictionary class]]) {
                // 字典转模型
                // 获取模型的类对象,调用modelWithDict
                // 模型的类名已知,就是成员属性的类型
                
                // 获取成员属性类型
                NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
                // 生成的是这种@"@\"User\"" 类型 -》 @"User"  在OC字符串中 \" -> ",\是转义的意思,不占用字符
                // 裁剪类型字符串
                NSRange range = [type rangeOfString:@"\""];
                
                type = [type substringFromIndex:range.location + range.length];
                
                range = [type rangeOfString:@"\""];
                
                // 裁剪到哪个角标,不包括当前角标
                type = [type substringToIndex:range.location];
                // 根据字符串类名生成类对象
                Class modelClass = NSClassFromString(type);
                
                
                if (modelClass) { // 有对应的模型才需要转
                    
                    // 把字典转模型
                    value  =  [modelClass modelWithDict:value];
                }
                
                
            }
            
            // 三级转换:NSArray中也是字典,把数组中的字典转换成模型.
            // 判断值是否是数组
            if ([value isKindOfClass:[NSArray class]]) {
                // 判断对应类有没有实现字典数组转模型数组的协议
                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 modelWithDict:dict];
                        [arrM addObject:model];
                    }
                    
                    // 把模型数组赋值给value
                    value = arrM;
                    
                }
            }
            
            
            if (value) { // 有值,才需要给模型的属性赋值
                // 利用KVC给模型中的属性赋值
                [objc setValue:value forKey:key];
            }
            
        }
        
        return objc;
    }
    

    -(NSDictionary *)arrayContainModelClass{
    NSDictionary *dic=@{@"aa":@"sta"};
    return dic;
    }
    这个方法主要是确定三级转换时,用哪个类接收

    - 二级转换时,如果用类去接收字典,那么需要重写setter方法
    
    
    测试:
    

    // 解析Plist文件
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil];
    NSDictionary *statusDict = [NSDictionary dictionaryWithContentsOfFile:filePath];
    // 获取字典数组NSArray *dictArr = statusDict[@"statuses"];
    // 自动生成模型的属性字符串//
    [NSObject resolveDict:dictArr[0][@"user"]];

    _statuses = [NSMutableArray array];
    // 遍历字典数组
    for (NSDictionary *dict in dictArr) {
    Status *status = [Status modelWithDict:dict];
    [_statuses addObject:status];
    }
    // 测试数据
    NSLog(@"%@ %@",_statuses,[_statuses[0] user]);

    }

    相关文章

      网友评论

      • WARRON:字典换模型没有 swift 版

      本文标题:OC和Swift中的Runtime

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