• iOS 手动打造JSON Model转换库


    前一段时间学习了Runtime,对类和对象的结构,和一些消息转发有一些自己的理解,现在希望简单的应用下,就决定自己写一个简单的JSON与Model的相互转化,现在总结下。

    建议查看

    • 参考资料 :Runtime学习笔记

      http://lastdays.cn/2016/02/22/runtime/

    • 项目地址: LYModelData

      https://github.com/MrLoong/LYModelData

    观察下面这个JSON数据和Model数据

    NSString *girlFriend = @"白菜";

    id parmenters = @{

            @"girlFriend":girlFriend,

            @"age":@22.1,

            @"name":@"Lastdays",

            @"time":@"2016-03-18 5:55:49 +0000"

    };

    @interface Model : NSObject

    @property NSNumber *age;

    @property NSString *name;

    @property NSString *girlFriend;

    @property NSData *time;

    @end

    开始的时候仔细想了一下,如何能够动态的去添加属性值,并且根据对应的属性进行赋值,还要保证类型正确,这是我最开始考虑的问题。但是最核心问题就是动态实现。

    我们一步一步来解决问题,首先我们先获取Model属性,取得Model的一些信息

    获取Model属性

    runtime提供了class_copyPropertyList来获取属性列表,OK,我们可以来看一下用它获取的数据是什么样的?查看runtime源码

    /***********************************************************************

    * class_copyPropertyList. Returns a heap block containing the

    * properties declared in the class, or nil if the class

    * declares no properties. Caller must free the block.

    * Does not copy any superclass's properties.

    **********************************************************************/

    objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)

    {

        old_property_list *plist;

        uintptr_t iterator = 0;

        old_property **result = nil;

        unsigned int count = 0;

        unsigned int p, i;

        if (!cls) {

            if (outCount) *outCount = 0;

            return nil;

        }

        mutex_locker_t lock(classLock);

        iterator = 0;

        while ((plist = nextPropertyList(cls, &iterator))) {

            count += plist->count;

        }

        if (count > 0) {

            result = (old_property **)malloc((count+1) * sizeof(old_property *));

            p = 0;

            iterator = 0;

            while ((plist = nextPropertyList(cls, &iterator))) {

                for (i = 0; i < plist->count; i++) {

                    result[p++] = property_list_nth(plist, i);

                }

            }

            result[p] = nil;

        }

        if (outCount) *outCount = count;

        return (objc_property_t *)result;

    }

    typedef struct old_property *objc_property_t;

    struct old_property {

        const char *name;

        const char *attributes;

    };

    ```

    从上面的三段runtime源码中,课本上就能判断出,其实返回结果就是一些old_property,并且每个old_property中含有对应的name和其他信息。

    总结起来说就是**class_copyPropertyList**获取Model属性列表,属性列表里面的objc_property_t包含着这个属性的类型和名字等一些信息。

    根据刚才的分析设计出以下结构:

    ``` bash

    -(id)modelToJsonObject:(NSObject *)model{

        Class cls = self.class;

        unsigned int countProperty = 0;

        objc_property_t *propertys = class_copyPropertyList(cls,&countProperty);

        NSMutableDictionary *dic = [NSMutableDictionary new];

        for (unsigned int i = 0; i<countProperty; i++) {

            PropertyInfo *propertyInfo = [[PropertyInfo alloc] initWithProperty:propertys[i]];

            if (propertyInfo.propertyName!=nil) {

                dic[propertyInfo.propertyName] = [self LYModelSetJsonObjectWith:model propertyInfo:propertyInfo];

            }

        }

        return dic;

    }

    PropertyInfo也就是属性信息,我们将Model的所有属性存放到NSMutableDictionary中,key就是属性名,Value就是PropertyInfo。

    接下来开始获取Model的属性信息PropertyInfo

    我们可以通过property_getName来获取属性名,查看源码

    const char *property_getName(objc_property_t prop)

    {

        return oldproperty(prop)->name;

    }

    接下来就是获取属性的类型和一些其他的信息。获取属性的信息其实和上面的原理差不多,我们使用property_copyAttributeList,查看下它的源码

    objc_property_attribute_t *property_copyAttributeList(objc_property_t prop,

                                                          unsigned int *outCount)

    {

        if (!prop) {

            if (outCount) *outCount = 0;

            return nil;

        }

        mutex_locker_t lock(classLock);

        return copyPropertyAttributeList(oldproperty(prop)->attributes,outCount);

    }

    看到这里,不往下继续分析源码了,其实可以看到,attributes就是我们想要的信息,其实每个property也是有自己对应的attributes。

    这个attributes是什么样呢?翻看源码,找到了答案

    typedef struct {

        const char *name;      

        const char *value;        

    } objc_property_attribute_t;

    加一下断点,看看

    可以看到,name是T,Value是NSNumber,我们来获取下NSNumber这个属性类型。

    for (unsigned int i = 0; i<attrCount; i++) {

        if (attrs[i].name[0] == 'T') {

            size_t len = strlen(attrs[i].value);

            if (len>3) {

                char name[len - 2];

                name[len - 3] = '';

                memcpy(name, attrs[i].value + 2, len - 3);

                _typeClass = objc_getClass(name);

            }

        }

    }

    基本上我们想要的信息基本上都已经获取到了,现在接下来就是做动态设定。

    中间做个插曲简单的说下Objc是动态语言,[receiver message]的执行过程当中,[receiver message]是会被动态编译的,Objc是动态语言,因此它会想尽办法将编译连接推迟到运行时来做。runtime这个时实运行系统就是来执行编译后的代码。想详细了解,欢迎阅读Runtime学习笔记

    http://lastdays.cn/2016/02/22/runtime/

    在这个消息发送过程中,objc_msgSend充当着很重要的角色,所以我们可以主动触发objc_msgSend,来模拟getter,setter方法获取属性值,或者建立。

    我们通过SEL来定义选择器,选择器是什么?就是方法名的唯一标识符

    根据刚才的想法,编写的代码最后是这个样子

    -(instancetype)initWithProperty:(objc_property_t)property{

        _property = property;

        const char *name = property_getName(property);

        if (name) {

            _propertyName = [NSString stringWithUTF8String:name];

        }

        unsigned int attrCount;

        objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount);

        for (unsigned int i = 0; i<attrCount; i++) {

            if (attrs[i].name[0] == 'T') {

                size_t len = strlen(attrs[i].value);

                if (len>3) {

                    char name[len - 2];

                    name[len - 3] = '';

                    memcpy(name, attrs[i].value + 2, len - 3);

                    _typeClass = objc_getClass(name);

                }

            }

        }

        NSString *setter = [NSString stringWithFormat:@"set%@%@:", [_propertyName substringToIndex:1].uppercaseString, [_propertyName substringFromIndex:1]];

        _setter =  NSSelectorFromString(setter);

        _getter = NSSelectorFromString(_propertyName);

        return self;

    }

    基本的准备工作,和一些问题都解决了,接下来可以写功能了。

    JSON转Model

    根据刚才说的,我们可以主动触发objc_msgSend,来模拟setter方法建立属性值。设计出以下方法

    -(void)LYModelSetPropertyWithModel:(id) model value:(id)value propertyInfo:(PropertyInfo *) propertyInfo{

        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, propertyInfo.setter, value);

    }

    我们将Model的所有属性存放到NSMutableDictionary中,key就是属性名,Value就是PropertyInfo。

    现在就可以动态设定了

    -(BOOL)LYModelSelectProperties:(NSDictionary *)dictonary{

        ClassInfo *cls = [[ClassInfo alloc]initWithClass:object_getClass(self)];

        id key, value;

        NSArray *keys = [dictonary allKeys];

        NSUInteger count = [keys count];

        for (int i = 0; i < count; i++){

            key = [keys objectAtIndex: i];

            value = [dictonary objectForKey: key];

            if (cls.propertyInfo[key]) {

                [self LYModelSetPropertyWithModel:self value:value propertyInfo:cls.propertyInfo[key]];

            }

        }

        return YES;

    }

    完成动态设定

    Model转JSON

    原理跟JSON转Model

    我们可以主动触发objc_msgSend,来模拟getter方法来获取属性值。

    -(id)LYModelSetJsonObjectWith:(id)model propertyInfo:(PropertyInfo *)propertyInfo{

        id value = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyInfo.getter);

        return value;

    }

    建立NSDictionary

    -(id)modelToJsonObject:(NSObject *)model{

        Class cls = self.class;

        unsigned int countProperty = 0;

        objc_property_t *propertys = class_copyPropertyList(cls,&countProperty);

        NSMutableDictionary *dic = [NSMutableDictionary new];

        for (unsigned int i = 0; i<countProperty; i++) {

            PropertyInfo *propertyInfo = [[PropertyInfo alloc] initWithProperty:propertys[i]];

            if (propertyInfo.propertyName!=nil) {

                dic[propertyInfo.propertyName] = [self LYModelSetJsonWith:model propertyInfo:propertyInfo];

            }

        }

        return dic;

    }

    完成获取

    测试

    NSString *girlFriend = @"白菜";

        id parmenters = @{

                          @"girlFriend":girlFriend,

                          @"age":@22.1,

                          @"name":@"Lastdays",

                          @"time":@"2016-03-18 5:55:49 +0000"

                          };

        Model *model = [Model LYModelWithJSON:parmenters];

        NSLog(@"%@",model.girlFriend);

        NSLog(@"%@",model.name);

        NSLog(@"%@",model.age);

        NSLog(@"%@",model.time);

        NSLog(@"========================================");

        NSDictionary *jsonObject= [model LYModelToJson];

        NSLog(@"%@",jsonObject);

    结果:

    总结

    简单的JSON Model转换库,关键点就是在于对runtime的理解。就当自己的一个小练习,后续会继续维护,让它对更多类型进行支持。代码结构上可能不是那么好,后续会将整体的结构重新设计下,增加可读性,也欢迎来提出建议。

  • 相关阅读:
    能飞英语学习软件学习实践
    英语学习方式总结与实践
    Hello World
    centos 7.6中搭建samba共享服务
    PHP漏洞全解(一)PHP网站的安全性问题
    MySQL查询语句练习题
    在PHP中使用CURL实现GET和POST请求的方法
    js数组的操作大全
    php四种基础算法:冒泡,选择,插入和快速排序法
    Linux查看端口使用状态及启动
  • 原文地址:https://www.cnblogs.com/fengmin/p/5453815.html
Copyright © 2020-2023  润新知