当前位置: 首页 > 编程日记 > 正文

runtime自动归档/解档

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

前言

善用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

支持并捐助

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

支付宝捐助微信捐助
imageimage

相关文章:

Ivanti 洞察职场新趋势:71% 的员工宁愿放弃升职也要选择随处工作

近日&#xff0c;为从云端到边缘的 IT 资产提供检测、管理、保护和服务的自动化平台供应商 Ivanti 公布了其年度无处不在的办公空间&#xff08; Everywhere Workplace&#xff09; 调查结果。这项调查是Ivanti与全球“未来工作”专家共同完成的&#xff0c;调查范围涵盖 6100 …

Shippable和Packet合作提供原生ARM CI/CD

DevOps自动化平台Shippable和裸金属云服务提供商Packet联合发布了一种新的持续集成和交付&#xff08;CI/CD&#xff09;托管服务&#xff0c;适用于在Armv8-A架构上开发软件应用的开发人员。该解决方案支持开源和商业软件项目&#xff0c;用于在Packet提供的基于ARM的云服务上…

阿里云ECS架设***过程总结

原文地址:最近开发移动项目,数据库服务是架设在电信服务器上,可怜我的联通网络本地调试直接x碎了一地!!度娘相关资料后,最终决定在阿里云ECS上架设作为跳板来访问电信服务器!一.原理1.阿里云ECS上架设.2.本地连接使用拨号到阿里云ECS.3.使用阿里云ECS网络访问电信服务器.使用前…

MYSQL的MERGE存储引擎

MYSQL的引擎不是一般的多&#xff0c;这次说到的是MERGE&#xff0c;这个引擎有很多特殊的地方&#xff1a; MERGE引擎类型允许你把许多结构相同的表合并为一个表。然后&#xff0c;你可以执行查询&#xff0c;从多个表返回的结果就像从一个表返回的结果一样。每一个合并的表必…

Pandas SQL 语法归纳总结,真的太全了

作者 | 俊欣来源 | 关于数据分析与可视化对于数据分析师而言&#xff0c;Pandas与SQL可能是大家用的比较多的两个工具&#xff0c;两者都可以对数据集进行深度的分析&#xff0c;挖掘出有价值的信息&#xff0c;但是二者的语法有着诸多的不同&#xff0c;今天小编就来总结归纳一…

分布式RPC实践--Dubbo基础篇

2019独角兽企业重金招聘Python工程师标准>>> 简介 Dubbo是阿里巴巴开源的一个高性能的分布式RPC框架&#xff0c;整个框架的核心原理来源于生产者与消费者的运作模型&#xff1b;框架的核心分4大部分&#xff1a; 1. 服务注册中心 注册中心主要用于保存生产者消费者…

又居家办公了,要签合同怎么办?

本篇文章暨 CSDN《中国 101 计划》系列数字化转型场景之一。 《中国 101 计划——探索企业数字化发展新生态》为 CSDN 联合《新程序员》、GitCode.net 开源代码仓共同策划推出的系列活动&#xff0c;寻访一百零一个数字化转型场景&#xff0c;聚合呈现并开通评选通道&#xff0…

lombox的用法(省去了set/get/NoArgsConstructor/AllArgsConstructor)

1、环境的搭建&#xff0c;在eclipse下的eclipse.ini中添加以下参数&#xff0c;-Xbootclasspath/a:C:\repository\org\projectlombok\lombok\1.16.6\lombok-1.16.6.jar-javaagent:C:\repository\org\projectlombok\lombok\1.16.6\lombok-1.16.6.jar重启你的eclipse.2、将lombo…

mysql 压力测试脚本

#创建表DEPTCREATE TABLE dept( /*部门表*/deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,dname VARCHAR(20) NOT NULL DEFAULT "",loc VARCHAR(13) NOT NULL DEFAULT "") ENGINEMyISAM DEFAULT CHARSETutf8 ;#创建表EMP雇员CREATE TABLE emp(empno…

C++语言学习(十二)——C++语言常见函数调用约定

C语言学习&#xff08;十二&#xff09;——C语言常见函数调用约定 一、C语言函数调用约定简介 C /C开发中&#xff0c;程序编译没有问题&#xff0c;但链接的时候报告函数不存在&#xff0c;或程序编译和链接都没有错误&#xff0c;但只要调用库中的函数就会出现堆栈异常等现象…

PHP代码保护——Zend Guard

Zend Guard的作用&#xff0c;就是用编译处理的方式来保护PHP源代码免于被反编译查看、未经授权的定制修改、未经许可的使用和重新发布等。而且&#xff0c;它是PHP的东家Zend公司开发的&#xff0c;是完全为PHP量身定做的保护神。 下面&#xff0c;请大家就和我一起来学习使用…

Python 2.4 递归函数

递归函数在函数内部&#xff0c;可以调用其他函数。如果一个函数在内部调用本身&#xff0c;这个函数就是递归函数。举个例子&#xff1a;计算阶乘n!1*2*3*4*5*...*n&#xff0c;用函数fact(n)表示&#xff0c;可以看出fact(n)n!f(n-1)*n所以&#xff0c;fact(n)可以表示为n*fa…

生于俄罗斯的 Web 服务器王者 Nginx,现宣布俄罗斯禁止贡献!

作者 | 苏宓出品 | CSDN不久之前&#xff0c;一些底层工具、软件、开源项目相继宣布在俄罗斯停服&#xff0c;彼时也有不少开发者呼吁 Nginx 是时候进行反限制了。万万没想到&#xff0c;就在国际局势发生改变的一个月后&#xff0c;Nginx 动了手&#xff0c;但是有些「意料之外…

OCP换考题了,052新考题及答案整理-第17题

17、Which two statements are true about tablespaces? A) A database can contain multiple undo tablespaces. B) A database can contain only a single temporary tablespace. C) A database instance stores undo data In the SYSTEM tablespace If no undo tablespace …

linux的mount(挂载)命令详解

linux下挂载&#xff08;mount&#xff09;光盘镜像文件、移动硬盘、U盘、Windows和NFS网络共享 linux是一个优秀的开放源码的操作系统&#xff0c;可以运行在大到巨型小到掌上型各类计算机系统上&#xff0c;随着 linux系统的日渐成熟和稳定以及它开放源代码特有的优越性&…

GPT-3 再更新,新增编辑和插入文本功能,简直不要太好用!

编译 | 禾木木出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;GPT-3 是 OpenAL 提出的基于上下文的超大规模自然处理深度学习模型。这意味着如果你给 GPT-3 某些上下文内容时&#xff0c;它会试图去填充其余内容。例如给出句子的前部分&#xff0c;它会推测出下半部分…

scala akka 修炼之路5(scala特质应用场景分析)

scala中特质定义&#xff1a;包括一些字段&#xff0c;行为(方法/函数/动作)和一些未实现的功能接口的集合&#xff0c;能够方便的实现扩展或混入到已有类或抽象类中。 scala中特质(trait)是一个非常实用的特性&#xff0c;在程序设计中能够 更好的抽象现实。使程序更关注各自功…

6.2 sql安全性

最后的例子将显示如何通过现有证书创建-个新的用户。本章稍后会介绍证书&#xff0c;但在 这个例子中&#xff0c;首先创建证书&#xff0c;然后创建用户&#xff1a; USE AdventureWorks2008; CREATE CERTIFICATE SalesCert ENCRYPTION BY PASSWORD Pssw0rd WITH SUBJECT fSa…

2022,人工智能开启未来新密码

作者 | 剑客阿良_ALiang&#xff08;胡逸&#xff09; 出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09; 购买大型电器、汽车&#xff0c;你是否会询问有没有智能语音功能&#xff1f;是的&#xff0c;潜移默化中人们已经不再将人工智能当作魔术&#xff0c;而是习以为…

PHP Socket配置以及实例

2个php测试文件 server.php <?php//phpinfo();//确保在连接客户端时不会超时set_time_limit(0);$ip 127.0.0.1;$port 1935;/*-------------------------------* socket通信整个过程-------------------------------* socket_create* socket_bind* socket_lis…

Windows下msysGit使用及相关配置

Windows下msysGit使用及相关配置

使用可信证书为windows RDP服务提供加密

2019独角兽企业重金招聘Python工程师标准>>> 0x01 前言 在windows server下可以通过配置远程桌面服务为RDP连接提供有效的数字证书以便提高安全性。 可是个人用户或并没有部署域控制器的用户是无法通过这种途径修改RDP所使用的数字证书&#xff0c;在不安全的环境中…

JavaScript跨域总结与解决办法

JavaScript跨域总结与解决办法 什么是跨域1、document.domainiframe的设置2、动态创建script3、利用iframe和location.hash4、window.name实现的跨域数据传输5、使用HTML5 postMessage6、利用flash本文来自网络&#xff08;http://f2e.me/200904/cross-scripting/&#xff0c;该…

这几个 Python 的小技巧,你会么?

来源丨Python小二作者 Peter Gleeson 是一名数据科学家&#xff0c;日常工作几乎离不 python。一路走来&#xff0c;他积累了不少有用的技巧和 tips&#xff0c;现在就将这些技巧分享给大家。这些技巧将根据其首字母按 A-Z 的顺序进行展示。ALL OR ANYPython 之所以成为这么一门…

如何创建可扩展表视图中的iOS 学习和拓展优化(有待更新)

首先介绍老外的文章&#xff1a;《How To Create an Expandable Table View in iOS》这是老外用Swift实现的&#xff0c;对应的老外github项目源码&#xff1a;https://github.com/appcoda/expandable-table-view小编经过学习了老外的Expandable Table View然后用Objective-C实…

String、StringBuffer、StringBuilder的理解

问题: 理解 Java的字符串&#xff0c;String、StringBuffer、StringBuilder 有什么区别&#xff1f; 知识点 字符串设计和实现考量 String是Immutable(线程安全、字符串常量池复用)。Immutable对象在拷贝时候不需要额外复制数据。至于为什么imumutable&#xff0c;源码如下&…

Linux(centos6.0)下安装Node.js以及使用

Linux下(centos6.0)安装Node.js1.wget http://nodejs.org/dist/node-v0.6.9.tar.gz tar zxvf node-v0.6.9.tar.gz cd node-v0.6.9 ./configure --prefix/usr/local/node ----------安装提示-------------Checking for program g or c : not found Checking for progr…

Pandas 中的这些函数/属性将被 deprecated

作者 | luanhz来源丨小数志导读Pandas对于日常数据分析和处理来说是最常用的工具&#xff08;没有之一&#xff09;&#xff0c;笔者之前也总结分享了很多相关用法和技巧。与之不同&#xff0c;今天本文来介绍几个已经在函数文档中列入"deprecated"的函数/属性&#…

2016.01.04 论文改重

今天的任务是修改查重的问题&#xff0c;另外加入参考文献。 其中&#xff0c;上午的时间完成论文查重。 下午的时间完成参考文献的丰富和标记。 晚上的时间完成DOM基础&#xff08;李炎恢&#xff09;的学习 预计晚上八点到晚上十点 优先级&#xff1a;论文查重&#xff0c;参…

完美数据迁移-MongoDB Stream的应用

目录 一、背景介绍二、常见方案1. 停机迁移2. 业务双写3. 增量迁移三、Change Stream 介绍监听的目标变更事件四、实现增量迁移五、后续优化小结附参考文档一、背景介绍 最近微服务架构火的不行&#xff0c;但本质上也只是风口上的一个热点词汇。 作为笔者的经验来说&#xff0…