原文出自:标哥的技术博客

前言

在开发中必不可少的模型与字典互转,但是一直以来都是使用他人的库,从来没有研究其原理或者说深究其所以然。现在,在这里我们一起来学习通过runtime完成模型与字典的互转。

声明Model

在开始介绍详细API之前,我们先来声明一个模型类HYBTestModel,这个类提供了根据字典转换成模型类对象的功能,还提供了将模型类转换成字典的功能:

//
//  HYBTestModel.h
//  RuntimeDemo
//
//  Created by huangyibiao on 15/12/28.
//  Copyright © 2015年 huangyibiao. All rights reserved.
//#import <Foundation/Foundation.h>@protocol HYBEmptyPropertyProperty <NSObject>// 设置默认值,若为空,则取出来的就是默认值
- (NSDictionary *)defaultValueForEmptyProperty;@end@interface HYBTestModel : NSObject <HYBEmptyPropertyProperty>@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, strong) NSNumber *count;
@property (nonatomic, assign) int    commentCount;
@property (nonatomic, strong) NSArray *summaries;
@property (nonatomic, strong) NSDictionary *parameters;
@property (nonatomic, strong) NSSet *results;@property (nonatomic, strong) HYBTestModel *testModel;// 只读属性
@property (nonatomic, assign, readonly) NSString *classVersion;// 通过这个方法来实现自动生成model
- (instancetype)initWithDictionary:(NSDictionary *)dictionary;// 转换成字典
- (NSDictionary *)toDictionary;// 测试
+ (void)test;@end

我们这里声明了几种类型,主要是模型中的数组、字典、集合、对象属性,我们最后要转换对象成字典。

实现代码

在我们讲解之前,先把代码全部放出来,我们再一一讲解:

#import "HYBTestModel.h"
#import <objc/runtime.h>
#import <objc/message.h>@implementation HYBTestModel- (instancetype)initWithDictionary:(NSDictionary *)dictionary {if (self = [super init]) {for (NSString *key in dictionary.allKeys) {id value = [dictionary objectForKey:key];if ([key isEqualToString:@"testModel"]) {HYBTestModel *testModel = [[HYBTestModel alloc] initWithDictionary:value];value = testModel;self.testModel = testModel;continue;}SEL setter = [self propertySetterWithKey:key];if (setter != nil) {((void (*)(id, SEL, id))objc_msgSend)(self, setter, value);}}}return self;
}- (NSDictionary *)toDictionary {unsigned int outCount = 0;objc_property_t *properties = class_copyPropertyList([self class], &outCount);if (outCount != 0) {NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:outCount];for (unsigned int i = 0; i < outCount; ++i) {objc_property_t property = properties[i];const void *propertyName = property_getName(property);NSString *key = [NSString stringWithUTF8String:propertyName];// 继承于NSObject的类都会有这几个在NSObject中的属性if ([key isEqualToString:@"description"]|| [key isEqualToString:@"debugDescription"]|| [key isEqualToString:@"hash"]|| [key isEqualToString:@"superclass"]) {continue;}// 我们只是测试,不做通用封装,因此这里不额外写方法做通用处理,只是写死测试一下效果if ([key isEqualToString:@"testModel"]) {if ([self respondsToSelector:@selector(toDictionary)]) {id testModel = [self.testModel toDictionary];if (testModel != nil) {[dict setObject:testModel forKey:key];}continue;}}SEL getter = [self propertyGetterWithKey:key];if (getter != nil) {// 获取方法的签名NSMethodSignature *signature = [self methodSignatureForSelector:getter];// 根据方法签名获取NSInvocation对象NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];// 设置target[invocation setTarget:self];// 设置selector[invocation setSelector:getter];// 方法调用[invocation invoke];// 接收返回的值__unsafe_unretained NSObject *propertyValue = nil;[invocation getReturnValue:&propertyValue];//        id propertyValue = [self performSelector:getter];if (propertyValue == nil) {if ([self respondsToSelector:@selector(defaultValueForEmptyProperty)]) {NSDictionary *defaultValueDict = [self defaultValueForEmptyProperty];id defaultValue = [defaultValueDict objectForKey:key];propertyValue = defaultValue;}}if (propertyValue != nil) {[dict setObject:propertyValue forKey:key];}}}free(properties);return dict;}free(properties);return nil;
}- (SEL)propertyGetterWithKey:(NSString *)key {if (key != nil) {SEL getter = NSSelectorFromString(key);if ([self respondsToSelector:getter]) {return getter;}}return nil;
}- (SEL)propertySetterWithKey:(NSString *)key {NSString *propertySetter = key.capitalizedString;propertySetter = [NSString stringWithFormat:@"set%@:", propertySetter];// 生成setter方法SEL setter = NSSelectorFromString(propertySetter);if ([self respondsToSelector:setter]) {return setter;}return nil;
}#pragma mark - HYBEmptyPropertyProperty
- (NSDictionary *)defaultValueForEmptyProperty {return @{@"name" : [NSNull null],@"title" : [NSNull null],@"count" : @(1),@"commentCount" : @(1),@"classVersion" : @"0.0.1"};
}+ (void)test {NSMutableSet *set = [NSMutableSet setWithArray:@[@"可变集合", @"字典->不可变集合->可变集合"]];NSDictionary *dict = @{@"name"  : @"标哥的技术博客",@"title" : @"http://www.henishuo.com",@"count" : @(11),@"results" : [NSSet setWithObjects:@"集合值1", @"集合值2", set , nil],@"summaries" : @[@"sm1", @"sm2", @{@"keysm": @{@"stkey": @"字典->数组->字典->字典"}}],@"parameters" : @{@"key1" : @"value1", @"key2": @{@"key11" : @"value11", @"key12" : @[@"三层", @"字典->字典->数组"]}},@"classVersion" : @(1.1),@"testModel" : @{@"name"  : @"标哥的技术博客",@"title" : @"http://www.henishuo.com",@"count" : @(11),@"results" : [NSSet setWithObjects:@"集合值1", @"集合值2", set , nil],@"summaries" : @[@"sm1", @"sm2", @{@"keysm": @{@"stkey": @"字典->数组->字典->字典"}}],@"parameters" : @{@"key1" : @"value1", @"key2": @{@"key11" : @"value11", @"key12" : @[@"三层", @"字典->字典->数组"]}},@"classVersion" : @(1.1)}};HYBTestModel *model = [[HYBTestModel alloc] initWithDictionary:dict];NSLog(@"%@", model);NSLog(@"model->dict: %@", [model toDictionary]);
}@end

细节讲解

注意到这一行代码了吗?这是将对应的值赋值给对应的属性:

((void (*)(id, SEL, id))objc_msgSend)(self, setter, value)

我们需要明确一点,objc_msgSend函数是用于发送消息的,而这个函数是可以很多个参数的,但是我们必须手动转换成对应类型参数,比如上面我们就是强制转换objc_msgSend函数类型为带三个参数且返回值为void函数,然后才能传三个参数。如果我们直接通过objc_msgSend(self, setter, value)是报错,说参数过多。

(void (*)(id, SEL, id)这是C语言中的函数指针,如果不了解C,没有关系,我们只需要记住参数列表前面是一个(*)这样的就是对应函数指针了。

生成Setter方法

我们要通过objc_msgSend函数来发送消息给对象,然后通过属性的setter方法来赋值,那么我们要生成setter选择器:

- (SEL)propertySetterWithKey:(NSString *)key {NSString *propertySetter = key.capitalizedString;propertySetter = [NSString stringWithFormat:@"set%@:", propertySetter];// 生成setter方法SEL setter = NSSelectorFromString(propertySetter);if ([self respondsToSelector:setter]) {return setter;}return nil;
}

这里就是生成属性的setter选择器。我们知道,系统生成属性的setter方法的规范是setKey:这样的格式,因此我们只要按照同样的规则生成setter就可以了。另外我们还需要判断是否可以响应此setter方法。

模型中有模型属性

对于本demo中,模型中还有模型属性,那么我们应该如何来赋值呢?其实,我们需要注意一点,一定要给模型属性分配内存,否则看起来赋值了,但是对象还是空。

if ([key isEqualToString:@"testModel"]) {HYBTestModel *testModel = [[HYBTestModel alloc] initWithDictionary:value];value = testModel;self.testModel = testModel;continue;
}

这里是在for循环中的,我们一定要注意加上continue,否则下边可能会将其值设置为空哦。

生成Getter方法

我们可以通过NSSelectorFromString函数来生成SEL选择器,当然我们也可以通过@selector()生成SEL选择器,但是我们这里只能使用前者:

- (SEL)propertyGetterWithKey:(NSString *)key {if (key != nil) {SEL getter = NSSelectorFromString(key);if ([self respondsToSelector:getter]) {return getter;}}return nil;
}

模型转字典

首先,我们需要先获取所有的属性,以便获取属性值:

unsigned int outCount = 0;
objc_property_t *properties = class_copyPropertyList([self class], &outCount);

objc_property_t类型代表属性,它是一个结构体。通过函数class_copyPropertyList可以获取对象的属性列表及属性的个数。

在遍历属性列表时,我们通过这样获取名称:

objc_property_t property = properties[i];
const void *propertyName = property_getName(property);
NSString *key = [NSString stringWithUTF8String:propertyName];

由于继承于NSObject的对象,都有这几个属性,因此我们需要过滤掉:

// 继承于NSObject的类都会有这几个在NSObject中的属性
if ([key isEqualToString:@"description"]|| [key isEqualToString:@"debugDescription"]|| [key isEqualToString:@"hash"]|| [key isEqualToString:@"superclass"]) {continue;
}

调用getter方法获取值

我们要通过runtime获取值,常用的有两种方式:

  • 通过performSelector方法
  • 通过NSInvocation对象

下面是通过NSInvocation的方法,流程可以这样:先获取方法签名,然后根据方法签名生成NSInvocation对象,设置targetSEL,然后调用,最后获取返回值:

SEL getter = [self propertyGetterWithKey:key];
if (getter != nil) {// 获取方法的签名NSMethodSignature *signature = [self methodSignatureForSelector:getter];// 根据方法签名获取NSInvocation对象NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];// 设置target[invocation setTarget:self];// 设置selector[invocation setSelector:getter];// 方法调用[invocation invoke];// 接收返回的值__unsafe_unretained NSObject *propertyValue = nil;[invocation getReturnValue:&propertyValue];//        id propertyValue = [self performSelector:getter];
}

如果是通过performSelector方式,一行代码就可以了,但是会提示有内存泄露的风险,通常使用上面这种方式,而不是直接使用下面这种方式:

id propertyValue = [self performSelector:getter];

我们这里还做了额外的处理,当属性值获取到是空的时候,我们可以通过协议指定默认值。当值为空时,我们就会使用默认值:

if (propertyValue == nil) {if ([self respondsToSelector:@selector(defaultValueForEmptyProperty)]) {NSDictionary *defaultValueDict = [self defaultValueForEmptyProperty];id defaultValue = [defaultValueDict objectForKey:key];propertyValue = defaultValue;}
}

模型属性转换成字典

我们这里的模型有一个属性也是一个模型,因此我们需要额外处理一下:

// 我们只是测试,不做通用封装,因此这里不额外写方法做通用处理,只是写死测试一下效果
if ([key isEqualToString:@"testModel"]) {if ([self respondsToSelector:@selector(toDictionary)]) {id testModel = [self.testModel toDictionary];if (testModel != nil) {[dict setObject:testModel forKey:key];}continue;}
}

注意,这里是写死的哦,因为我们这里写死了testModel,只是为了简化,如果要封装成通用的方法,那么就需要做更多的工作了。不过我们这里的目的是学习如何转,而不是封装成能用的库。因此,研究明白其原理才是目的。

测试

我们测试一下这样复杂的结构:

+ (void)test {NSMutableSet *set = [NSMutableSet setWithArray:@[@"可变集合", @"字典->不可变集合->可变集合"]];NSDictionary *dict = @{@"name"  : @"标哥的技术博客",@"title" : @"http://www.henishuo.com",@"count" : @(11),@"results" : [NSSet setWithObjects:@"集合值1", @"集合值2", set , nil],@"summaries" : @[@"sm1", @"sm2", @{@"keysm": @{@"stkey": @"字典->数组->字典->字典"}}],@"parameters" : @{@"key1" : @"value1", @"key2": @{@"key11" : @"value11", @"key12" : @[@"三层", @"字典->字典->数组"]}},@"classVersion" : @(1.1),@"testModel" : @{@"name"  : @"标哥的技术博客",@"title" : @"http://www.henishuo.com",@"count" : @(11),@"results" : [NSSet setWithObjects:@"集合值1", @"集合值2", set , nil],@"summaries" : @[@"sm1", @"sm2", @{@"keysm": @{@"stkey": @"字典->数组->字典->字典"}}],@"parameters" : @{@"key1" : @"value1", @"key2": @{@"key11" : @"value11", @"key12" : @[@"三层", @"字典->字典->数组"]}},@"classVersion" : @(1.1)}};HYBTestModel *model = [[HYBTestModel alloc] initWithDictionary:dict];NSLog(@"%@", model);NSLog(@"model->dict: %@", [model toDictionary]);
}

打印效果

模型转字典的效果:

2015-12-29 16:02:15.207 RuntimeDemo[40233:1083396] model->dict:     {classVersion = "0.0.1",count = 11,parameters =    {key1 = "value1",key2 =  {key11 = "value11",key12 =     ("三层","字典->字典->数组",),},},results =   {("集合值2","集合值1",{("可变集合","字典->不可变集合->可变集合",)},)},title = "http://www.henishuo.com",commentCount = 1,testModel =     {classVersion = "0.0.1",count = 11,parameters =    {key1 = "value1",key2 =  {key11 = "value11",key12 =     ("三层","字典->字典->数组",),},},results =   {("集合值2","集合值1",{("可变集合","字典->不可变集合->可变集合",)},)},title = "http://www.henishuo.com",commentCount = 1,name = "标哥的技术博客",summaries =     ("sm1","sm2",{keysm =     {stkey = "字典->数组->字典->字典",},},),},name = "标哥的技术博客",summaries =     ("sm1","sm2",{keysm =     {stkey = "字典->数组->字典->字典",},},),
}

转换是成功的,目的达到了!!!

注意,这里打印显示这样的格式,中文也能正常显示出来,是笔者提供了这样一个库,详细请阅读http://www.henishuo.com/ios-unicode-readable/

写在最后

本篇就介绍这么多,模型与字典互转的功能就可以自己尝试去实现了,对runtime的了解也更深入一步了!!!

关注我

如果在使用过程中遇到问题,或者想要与我交流,可加入有问必答QQ群:324400294

关注微信公众号:iOSDevShares

关注新浪微博账号:标哥Jacky

支持并捐助

如果您觉得文章对您很有帮助,希望得到您的支持。您的捐肋将会给予我最大的鼓励,感谢您的支持!

支付宝捐助 微信捐助

runtime模型与字典互转相关推荐

  1. YYModel 源码分析:模型转字典

    YYModel , 模型字典,自动转化, 模型转字典, 主要是通过运行时,把模型的属性名取出来,递归构建字典 YYModel 设计 4 个模型,记录类与其属性信息, 类 + 信息记录 类 属性 + 信 ...

  2. python xml字符串和dict字典互转

    python xml字符串和dict字典互转 字典 转 xml def trans_dict_to_xml(data_dict):"""字典转xml:param data ...

  3. Python编程语言学习:列表与字典互转的几大方法集锦、从列表中按顺序循环抽走一个元素输出剩余元素之详细攻略

    Python编程语言学习:列表与字典互转的几大方法集锦.从列表中按顺序循环抽走一个元素输出剩余元素之详细攻略 目录 列表与字典互转的几大方法集锦 T1.基于两个列表利用zip函数来构造字典 <

  4. 防止按钮重复点击 模型转字典 接口传参不能用汉字,要转码

    #pragma mark -- 发表评论 - (IBAction)publishBtn:(id)sender {if ([self.commentType isEqualToString:@" ...

  5. ios yymodel 将字典转数组模型_Python3 字典

    Python3 字典 Python AI开发实战营 - 一堂课快速认识Python机器学习 - 创客学院直播室​www.makeru.com.cnPython AI开发实战营 - Day1:建立pyt ...

  6. iOS开发JSON字符串和字典互转

    1.相关属性简述 NSJSONReadingOptions读取属性: typedef NS_OPTIONS(NSUInteger, NSJSONReadingOptions) {NSJSONReadi ...

  7. python字符串/元组/列表/字典互转

    #-*-coding:utf-8-*- #1.字典 dict = {'name': 'Zara', 'age': 7, 'class': 'First'}#字典转为字符串,返回:<type 's ...

  8. pyhton url参数和字典互转

    字典转url urllib.parse.urlencode(params, doseq=True) quote用于url的编码,而quote_plus用于post的data编码,所以而urlencod ...

  9. iOS 开发第三方库全集

    下拉刷新 EGOTableViewPullRefresh – 最早的下拉刷新控件. SVPullToRefresh – 下拉刷新控件. MJRefresh – 仅需一行代码就可以为UITableVie ...

最新文章

  1. 使用android ProgressBar和Toast生成一个界面
  2. 三层架构实现增删的简单实例
  3. 加速DeiT-S 60%+吞吐量!腾讯优图提出高性能Transformer加速方法
  4. C++ class实现Huffman树(完整代码)
  5. 带设计师去选材料_装修可别选“全包”!10个缺陷让你绝望!
  6. Lucas定理(求组合数,例题FZU2020,HDU3944)
  7. ②⓪②⓪ → ②⓪②①
  8. 迈向 HTTPS,HTTPS 到底解决了什么问题
  9. 【储留香系列】如何构建一个拖垮公司的备份系统
  10. 开源表单系统|Tduck填鸭表单docker部署详细教程
  11. 免费手机WAP网站大全
  12. 【数字信号调制】基于PCM编码和QAM调制系统附matlab代码
  13. 【高等数学】常用函数的n阶导数
  14. 宝塔nginx自编译云锁web防护教程
  15. 1.CDC绘图。包括加载图片,消去图片,设置图片一部分透明,不闪烁方式。
  16. python多个函数_请教:一个类中可以定义多个同名函数?
  17. 50个BA分析工具第四个-Business Case
  18. 推荐几款jQuery时间轴插件Timeline
  19. 【来日复制粘贴】数据透视表分类不同账龄
  20. linux devel包 和 非devel包的区别

热门文章

  1. 1010.在线视频—开源网管Nagios(三)使用Nagios监控服务器
  2. office excel单列数据类型不一致,导入时部分数据为空
  3. 使用Eclipse更新软件时出现Unable to locate secure storage module错误的解决方法
  4. Oracle创建表管理表
  5. 大数据+机器学习#x3D;天下无敌!
  6. java jodd 框架中发送email
  7. opengl 模板测试 glStencilOp glStencilFunc
  8. 微软:97%电子邮件属于垃圾邮件
  9. linux 套接字选项定义
  10. Qt Creator添加资源