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

前言

善用runtime,可以解决自动归档解档。想想以前归档是手动写的,确实太麻烦了。现在有了runtime,我们可以做到自动化了。本篇文章旨在学习如何通过runtime实现自动归档和解档,因此不会对所有类型适用,而是对我们指定的几种类型适用。

定义模型

我们这里只是写一个例子,用于学习如何用runtime实现自动归档以及解档,因此,我们需要定义一个模型类,然后在里面实现自动归档和解档。

我们这里只处理了普通的几种类型,这里只测试intNSStringconst void *NSNumberfloat类型。对于const void *是不支持kvc的,因此我们是不能通过kvc完成的,但是我们可以通过runtime发送消息实现。

声明头文件

我们首先得要定义一个类,声明一下我们要测试的类型。首先,我们必须要遵守协议NSCoding,这是归档必须要遵守的:

@interface HDFArchiveModel : NSObject <NSCoding>@property (nonatomic, assign) int    referenceCount;
@property (nonatomic, copy) NSString *archive;
@property (nonatomic, assign) const void *session;
@property (nonatomic, strong) NSNumber *totalCount;
// 注意,这里只是为了测试一下属性使用下划线的情况
@property (nonatomic, assign) float  _floatValue;+ (void)test;@end

实现代码

遵守了NSCoding协议之后,我们就可以在实现文件中实现-encodeWithCoder:方法来归档和-initWithCoder:解档。

实现代码如下:

#import <objc/runtime.h>
#import <objc/message.h>@implementation HDFArchiveModel- (void)encodeWithCoder:(NSCoder *)aCoder {unsigned int outCount = 0;Ivar *ivars = class_copyIvarList([self class], &outCount);for (unsigned int i = 0; i < outCount; ++i) {Ivar ivar = ivars[i];// 获取成员变量名const void *name = ivar_getName(ivar);NSString *ivarName = [NSString stringWithUTF8String:name];// 去掉成员变量的下划线ivarName = [ivarName substringFromIndex:1];// 获取getter方法SEL getter = NSSelectorFromString(ivarName);if ([self respondsToSelector:getter]) {const void *typeEncoding = ivar_getTypeEncoding(ivar);NSString *type = [NSString stringWithUTF8String:typeEncoding];// const void *if ([type isEqualToString:@"r^v"]) {const char *value = ((const void *(*)(id, SEL))(void *)objc_msgSend)((id)self, getter);NSString *utf8Value = [NSString stringWithUTF8String:value];[aCoder encodeObject:utf8Value forKey:ivarName];continue;}// intelse if ([type isEqualToString:@"i"]) {int value = ((int (*)(id, SEL))(void *)objc_msgSend)((id)self, getter);[aCoder encodeObject:@(value) forKey:ivarName];continue;}// floatelse if ([type isEqualToString:@"f"]) {float value = ((float (*)(id, SEL))(void *)objc_msgSend)((id)self, getter);[aCoder encodeObject:@(value) forKey:ivarName];continue;}id value = ((id (*)(id, SEL))(void *)objc_msgSend)((id)self, getter);if (value != nil && [value respondsToSelector:@selector(encodeWithCoder:)]) {[aCoder encodeObject:value forKey:ivarName];}}}free(ivars);
}- (instancetype)initWithCoder:(NSCoder *)aDecoder {if (self = [super init]) {unsigned int outCount = 0;Ivar *ivars = class_copyIvarList([self class], &outCount);for (unsigned int i = 0; i < outCount; ++i) {Ivar ivar = ivars[i];// 获取成员变量名const void *name = ivar_getName(ivar);NSString *ivarName = [NSString stringWithUTF8String:name];// 去掉成员变量的下划线ivarName = [ivarName substringFromIndex:1];// 生成setter格式NSString *setterName = ivarName;// 那么一定是字母开头if (![setterName hasPrefix:@"_"]) {NSString *firstLetter = [NSString stringWithFormat:@"%c", [setterName characterAtIndex:0]];setterName = [setterName substringFromIndex:1];setterName = [NSString stringWithFormat:@"%@%@", firstLetter.uppercaseString, setterName];}setterName = [NSString stringWithFormat:@"set%@:", setterName];// 获取getter方法SEL setter = NSSelectorFromString(setterName);if ([self respondsToSelector:setter]) {const void *typeEncoding = ivar_getTypeEncoding(ivar);NSString *type = [NSString stringWithUTF8String:typeEncoding];NSLog(@"%@", type);// const void *if ([type isEqualToString:@"r^v"]) {NSString *value = [aDecoder decodeObjectForKey:ivarName];if (value) {((void (*)(id, SEL, const void *))objc_msgSend)(self, setter, value.UTF8String);}continue;}// intelse if ([type isEqualToString:@"i"]) {NSNumber *value = [aDecoder decodeObjectForKey:ivarName];if (value != nil) {((void (*)(id, SEL, int))objc_msgSend)(self, setter, [value intValue]);}continue;} else if ([type isEqualToString:@"f"]) {NSNumber *value = [aDecoder decodeObjectForKey:ivarName];if (value != nil) {((void (*)(id, SEL, float))objc_msgSend)(self, setter, [value floatValue]);}continue;}// objectid value = [aDecoder decodeObjectForKey:ivarName];if (value != nil) {((void (*)(id, SEL, id))objc_msgSend)(self, setter, value);}}}free(ivars);}return self;
}+ (void)test {HDFArchiveModel *archiveModel = [[HDFArchiveModel alloc] init];archiveModel.archive = @"标哥学习自动归档";archiveModel.session = "http://www.henishuo.com";archiveModel.totalCount = @(123);archiveModel.referenceCount = 10;archiveModel._floatValue = 10.0;NSString *path = NSHomeDirectory();path = [NSString stringWithFormat:@"%@/archive", path];[NSKeyedArchiver archiveRootObject:archiveModeltoFile:path];HDFArchiveModel *unarchiveModel = [NSKeyedUnarchiver unarchiveObjectWithFile:path];}@end

自动归档解析

我们这里获取对象的成员变量,通过class_copyIvarList可以得到所有成员变量:

unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([self class], &outCount);

我们获取到的属性所生成的成员变量是带下划线,因此我们需要在获取到成员变量名称后,需要去掉下划线:

// 获取成员变量名
const void *name = ivar_getName(ivar);
NSString *ivarName = [NSString stringWithUTF8String:name];
// 去掉成员变量的下划线
ivarName = [ivarName substringFromIndex:1];

接下来,在归档的时候我们通过属性的getter方法来获取值,然后归档。但是,成员变量是是有类型的,并不是所有类型都可以归档,比如const void *就不支持归档,那么我们需要根据类型转换成支持归档的类型再存储。另外,我们可以通过ivar_getTypeEncoding函数获取成员变量的类型,但是这个类型不一定是我们常见的NSString之类的,可能会出现r^v这样代表const void *。这些都是runtime系统所定义的,因此它是固定的,我们只要去按照苹果给出的类型编码表就可以知道哪些字符代表什么类型了。

const void *typeEncoding = ivar_getTypeEncoding(ivar);
NSString *type = [NSString stringWithUTF8String:typeEncoding];// const void *
if ([type isEqualToString:@"r^v"]) {const char *value = ((const void *(*)(id, SEL))(void *)objc_msgSend)((id)self, getter);NSString *utf8Value = [NSString stringWithUTF8String:value];[aCoder encodeObject:utf8Value forKey:ivarName];continue;
}
// int
else if ([type isEqualToString:@"i"]) {int value = ((int (*)(id, SEL))(void *)objc_msgSend)((id)self, getter);[aCoder encodeObject:@(value) forKey:ivarName];continue;
}
// float
else if ([type isEqualToString:@"f"]) {float value = ((float (*)(id, SEL))(void *)objc_msgSend)((id)self, getter);[aCoder encodeObject:@(value) forKey:ivarName];continue;
}id value = ((id (*)(id, SEL))(void *)objc_msgSend)((id)self, getter);
if (value != nil && [value respondsToSelector:@selector(encodeWithCoder:)]) {[aCoder encodeObject:value forKey:ivarName];
}

像上面,我们只额外处理了const void *intfloat类型,当我们调用objc_msgSend函数时,一定要转换类型,如果类型不匹配无法转换则是会崩溃的。从这里看到objc_msgSend函数是不是很强大?是的,真的太强大了。一会可以是有返回值的,一会可以是带多个参数的,还可以是不带返回值的。

最后,我们就可以调用归档方法来实现了归档:

[NSKeyedArchiver archiveRootObject:archiveModeltoFile:path];

到此,我们归档的工作已经完成了。如果要写一个通用的自动归档扩展,那么我们就需要处理完苹果中所有的数据类型,包括基本类型、C指针类型等。

自动解档解析

不知道这里叫解档是否合适,但是似乎已经习惯这么叫了。要实现解档,其实就是实现NSCoding协议中的-initWithCoder:方法。

首先我们要生成setter方法,但是setter方法的生成是有规则的。若属性名称不带下划线,那么生成的setter方法就是set+属性名称,其中需要将属性名称的首字母变成大写。若属性名称带下划线,则不需要处理。我们看看如何生成:

// 那么一定是字母开头
if (![setterName hasPrefix:@"_"]) {NSString *firstLetter = [NSString stringWithFormat:@"%c", [setterName characterAtIndex:0]];setterName = [setterName substringFromIndex:1];setterName = [NSString stringWithFormat:@"%@%@", firstLetter.uppercaseString, setterName];
}
setterName = [NSString stringWithFormat:@"set%@:", setterName];

我们知道,苹果中的变量只是是字母、数字和下划线,其中第一个字符只能是字母或者是下划线。因此,上面判断第一个是否是下划线,若不是下划线,则说明一定是字母。然后我们需要获取首字母,以便转换成大写字母。当然,上面替换,我们也可以这样写:

[setterName stringByReplacingCharactersInRange:NSMakeRange(0, 0) withString:firstLetter.uppercaseString];

接下来就是判断属性的类型,然后发送消息。这里只说明设置const void *属性值:

if ([type isEqualToString:@"r^v"]) {NSString *value = [aDecoder decodeObjectForKey:ivarName];if (value) {((void (*)(id, SEL, const void *))objc_msgSend)(self, setter, value.UTF8String);}continue;
}

我们一定要强转objc_msgSend函数为(void (*)(id, SEL, const void *))这样的类型,然后其中第三个参数就是我们要设置的属性的值的类型。

最后,我们就可以通过解档方法来实现解档了:

HDFArchiveModel *unarchiveModel = [NSKeyedUnarchiver unarchiveObjectWithFile:path];

源代码

小伙伴们可以到我的GITHUB下载demo看看效果:https://github.com/CoderJackyHuang/RuntimeDemo

喜欢,就给个star吧!

写在最后

在学习通过runtime自动归档和解档的过程中,也遇到了不少问题。尤其是在生成setter方法时,由于调用了capitalizedString方法,结果将属性名称中只有首字母大写,其它全变成了小写,导致笔者调试了好久才发现这个小细节。这再次说明了,细节决定成败!!!

关注我

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

关注微信公众号:iOSDevShares

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

支持并捐助

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

支付宝捐助 微信捐助

runtime自动归档/解档相关推荐

  1. 使用偏好设置、属性列表、归档解档保存数据、恢复数据

    数据持久化就是将文件保存到硬盘,以便下次运行时可以读取或永久保存.iOS提供了以下几种持久化方案: NSUserDefaults (偏好设置) property list 即Plist (属性列表) ...

  2. IOS 利用运行时机制来进行归档解档

    前言:我们需要对一个类的属性进行归档解档的时候属性特别的多,这时候,我们就会写很多对应的代码,但是如果使用了runtime就可以动态设置,就会变的很轻松! 首先需要导入<objc/runtime ...

  3. iOS 利用归档解档实现类似微博及一些CRM类软件的草稿箱思路

    实际上就是写一个model类 然后将要存数据放到model中,然后将model添加到数组,然后对数组及数组中放的model归档,本地存储起来,解档,拿到数据在需要的地方显示出来 因为model是一个自 ...

  4. 【iOS数据持久化】归档解档(NSKeyedArchiver/ NSKeyedUnarchiver)

    简介 归档(Archive)也称为序列化(serialization),把对象转化为字节码,以文件的形式存储在磁盘上,只要遵循了NSCoding协议的对象都可以实现归档和解档(大部分foundatio ...

  5. 四种数据持久化方式(上) :属性列表与归档解档

    iOS中的永久存储,也就是在关机重新启动设备,或者关闭应用时,不会丢失数据.在实际开发应用时,往往需要持久存储数据的,这样用户才能在对应用进行操作后,再次启动能看到自己更改的结果与痕迹. iOS开发中 ...

  6. 数据存储之归档解档 NSKeyedArchiver NSKeyedUnarchiver

    在构建应用程序时,有一个重要的问题是如何在每次启动之间持久化数据,以便重现最后一次关闭应用前的状态.在iOS和OS X上,苹果提供了三种选择:Core Data.属性列表(Property List) ...

  7. 关于注册登陆时候进行的归档解档--严焕培

    #import <Foundation/Foundation.h> #import "SBModel.h" #import "AutoCoding.h&quo ...

  8. iOS 归档 解档使用总结

    2019独角兽企业重金招聘Python工程师标准>>> 1.比较 在使用plist进行数据存储和读取,只适用于系统自带的一些常用类型才能用,且必须先获取路径:     NSUserD ...

  9. iOS 归档 解档 unexpected class 问题

    解决 Error Domain=NSCocoaErrorDomain Code=4864 "value for key 'xxx' was of unexpected class 'xxx' ...

最新文章

  1. 钻井缸套排量_川庆钻探||合理化建议成果展示:用陶瓷代替金属,缸套使用寿命延长了10倍...
  2. CHANGE_DOCUMENT
  3. 读《Oracle DBA工作笔记》知识点-获取创建语句
  4. 深入理解ROS技术 【4】ROS下的模块详解(181-232)
  5. 计算机科学与技术的深度研究,专业深度分析--计算机科学与技术.docx
  6. java小编程--二分查找和插入排序
  7. [渝粤教育] 西南科技大学 管理信息系统 在线考试复习资料(1)
  8. 三年Java开发,尚学堂java马士兵全套
  9. C++学习之路 | PTA乙级—— 1051 复数乘法 (15 分)(精简)
  10. unity三维向量变化为角度_三维旋转
  11. 记录一下filter
  12. 计算机VFP试题答案,计算机二级《VFP》试题及答案
  13. 用计算机求值根号12345,手算开根号
  14. .NET获取微信openid
  15. PHP 第三方调用 UC_Center用户登录认证
  16. 三招教你如何搞定将qlv格式的腾讯视频转换为mp4格式
  17. MIT 18.06 +线性代数的几何意义+3Blue1Brown 笔记
  18. 区块链入门教程(9)--使用WeBASE-Front部署及调用合约
  19. 根据ip查服务器信息,根据IP查询云服务器
  20. matlab 热图,基于表格数据创建热图

热门文章

  1. SQL Relay开源的数据库池连接代理服务器
  2. datagrid分页问题(前后跳页)《控件版》
  3. 巧用CSS的Glow滤镜
  4. Gin源码解析和例子——路由
  5. 经典网络LeNet-5介绍及代码测试(Caffe, MNIST, C++)
  6. 线性回归介绍及分别使用最小二乘法和梯度下降法对线性回归C++实现
  7. CUDA Samples: image normalize(mean/standard deviation)
  8. vs2008部署问题
  9. 图像空间变换--imtransform
  10. Ruby DSL介绍及其在测试数据构造中的使用(1)