【iOS】数据持久化(二)之归档和解档(iOS 13以后)

在之前介绍的数据存储方法中,不管是NSUserDefaults还是plist文件都不能对自定义对象进行存储,OC提供的解归档恰好解决了这个问题

本片文章对 iOS13 以后的版本 归档和解档 进行介绍。老版本的解归档见这篇文章:【iOS】文件(对象数据)的归档和解档,参考这篇文章对比学习会对解归档有更好的理解

目录

    • 简介
    • 自定义对象的单个对象归档、解档
    • 多个对象解档归档
    • 嵌套类(复合类)
    • 解档 Success!!
    • 注意
    • MJExtension库(JSONModel、YYModel)


简介

在iOS中,对象的序列化和反序列化分别使用NSKeyedArchiverNSKeyedUnarchiver两个类,我们可以把一个类对象进行序列化然后保存到文件中,使用时再读取文件,把内容反序列化出来。这个过程通常也被称为 对象的编码(归档)和解码(解档)

  • 归档 — 将对象以文件(二进制数据)的形式保存到磁盘上中(也称序列化,持久化)
  • 解档 — 使用时从磁盘上读取该文件的保存路径,从而读取文件的内容(也称反序列化)

归档一般保存自定义对象、自定义对象数组,由于自定义对象不具有归档的性质,所以只有遵循了NSCoding协议的类才可以归档
由于决大多数支持存储数据的Foundation和Cocoa Touch类都遵循了NSCoding协议,因此,对于大多数OC提供的类来说,归档相对而言还是比较容易实现的。

对象归档的文件是保密的,在磁盘上无法查看文件中的内容,而属性列表是明文的,可以查看。通过文件归档产生的文件是不可见的,如果打开归档文件的话,内容是乱码的;ta不同于属性列表和plist文件是可见的,正因为不可见的缘故,使得这种持久性的数据保存更有可靠性

自定义对象的单个对象归档、解档

iOS 13中需要支持NSSecureCoding 协议(父协议为NSCoding)才能支持归档
请添加图片描述

  1. 自定义一个Person类并实现NSCoding 协议的方法
@interface Person : NSObject <NSSecureCoding>

@property (nonatomic, copy)NSString* name;
@property (nonatomic, assign)int age;
@property (nonatomic, assign)double weight;

@end

@implementation Person

//NSCoder是一个抽象类
//归档的协议方法
//将归档对象序列化
- (void)encodeWithCoder:(NSCoder *)coder {
    [coder encodeObject: self.name forKey: @"name"];
    [coder encodeInt: self.age forKey: @"age"];
    [coder encodeDouble: self.weight forKey: @"weight"];
}

//解档的协议方法
//将解档对象反序列化
- (instancetype)initWithCoder:(NSCoder *)coder {
    self = [super init];
    if (self) {
        self.name = [coder decodeObjectForKey: @"name"];
        self.age = [coder decodeIntForKey: @"age"];
        self.weight = [coder decodeDoubleForKey: @"weight"];
    }
    
    return self;
}

@end

//NSSecureCoding的协议方法
+ (BOOL)supportsSecureCoding {
    return YES;
}

  1. 初始化待归档对象并进行归档

+ (nullable NSData *)archivedDataWithRootObject:(id)object requiringSecureCoding:(BOOL)requiresSecureCoding error:(NSError **)error;

        Person* person = [[Person alloc] init];
        person.name = @"XY";
        person.age = 20;
        person.weight = 125.0;
        
        //归档成二进制数据流
        NSError* error;
        NSData* data1 = [NSKeyedArchiver archivedDataWithRootObject: person requiringSecureCoding: YES error: &error];
        if (error) {
            NSLog(@"归档错误:%@", error);
            return 0;
        }
        //写入指定路径(一般写入到沙盒,这里方便演示存到一个新的文件夹)
        [data1 writeToFile: @"/Users/jakey/Desktop/CS/Xcode/NSKeyedArchiverTest/test.archiver" atomically: YES];

Person对象被序列化后就会被保存在下方的文件中,但无法直接打开
请添加图片描述

通过终端命令打开后,可以看到内容是经过加密的,保证了数据的安全性
请添加图片描述

  1. 开始解档

+ (nullable id)unarchivedObjectOfClass:(Class)cls fromData:(NSData *)data error:(NSError **)error;

        //解档此二进制数据
        error = nil;
        NSData* data2 = [NSData dataWithContentsOfFile: @"/Users/jakey/Desktop/CS/Xcode/NSKeyedArchiverTest/test.archiver"];
        Person* unarchiverPerson = (Person *)[NSKeyedUnarchiver unarchivedObjectOfClass: [Person class] fromData: data2 error: &error];
        if (error) {
            NSLog(@"解档错误:%@", error);
        }
        NSLog(@"unarchiverPerson:%@", unarchiverPerson);

请添加图片描述

多个对象解档归档

将多个对象归档在同一个文件中:

  1. 初始化待归档对象并进行归档
Person* person1 = [[Person alloc] init];
person1.name = @"XY";
person1.age = 20;
person1.weight = 125.0;
Dog* dog1 = [[Dog alloc] init];
dog1.name = @"Bruce";
person1.dog = dog1;     
        
Person* person2 = [[Person alloc] init];
person2.name = @"Jacky";
person2.age = 21;
person2.weight = 130.0;
Dog* dog2 = [[Dog alloc] init];
dog2.name = @"Oudy";
person2.dog = dog2;
        
//创建归档对象
NSKeyedArchiver* archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding: NO];
        
//进行归档(编码)操作
[archiver encodeObject: person1 forKey: @"personOne"];
[archiver encodeObject: person2 forKey: @"personTwo"];
        
//将归档(序列化)后的数据写入指定文件中
[archiver.encodedData writeToFile: @"/Users/jakey/Desktop/CS/Xcode/NSKeyedArchiverTest/test.archiver" atomically: YES];
        
//结束归档
[archiver finishEncoding];
  1. 依次解档
//解档
NSData* data = [NSData dataWithContentsOfFile: @"/Users/jakey/Desktop/CS/Xcode/NSKeyedArchiverTest/test.archiver"];
NSKeyedUnarchiver* unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData: data error: nil];
unarchiver.requiresSecureCoding = NO;
        
Person* unchiverPerson1 = [unarchiver decodeObjectForKey: @"personOne"];
NSLog(@"%@ %d %lf %@", unchiverPerson1.name, unchiverPerson1.age, unchiverPerson1.weight, unchiverPerson1.dog.name);
Person* unchiverPerson2 = [unarchiver decodeObjectForKey: @"personTwo"];
NSLog(@"%@ %d %lf %@", unchiverPerson2.name, unchiverPerson2.age, unchiverPerson2.weight, unchiverPerson2.dog.name);

嵌套类(复合类)

现对于Person类,设置一个自定义对象dog属性,那么这个内层的Dog类也需要实现NSSecureCoding 协议,否则程序会崩溃:

在这里插入图片描述
上面也提到过,OC提供的类(比如这里的name)已经遵循了此协议,因此无需手动操作,但自定义的Dog类要手动添加协议函数:

@interface Dog : NSObject <NSSecureCoding>

@property (nonatomic, strong)NSString* name;

@end

- (void)encodeWithCoder:(NSCoder *)coder {
    [coder encodeObject: self.name forKey: @"dogName"];
}

- (instancetype)initWithCoder:(NSCoder *)coder {
    self = [super init];
    if (self) {
        self.name = [coder decodeObjectForKey: @"dogName"];
    }
    
    return self;
}

+ (BOOL)supportsSecureCoding {
    return YES;
}

以下是复合类解归档完整代码:

#import <Foundation/Foundation.h>
#import "Person.h"
#import "Dog.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person* person1 = [[Person alloc] init];
        person1.name = @"XY";
        person1.age = 20;
        person1.weight = 125.0;
        Dog* dog1 = [[Dog alloc] init];
        dog1.name = @"Bruce";
        person1.dog = dog1;
        
        
        Person* person2 = [[Person alloc] init];
        person2.name = @"Jacky";
        person2.age = 21;
        person2.weight = 130.0;
        Dog* dog2 = [[Dog alloc] init];
        dog2.name = @"Oudy";
        person2.dog = dog2;
        
        //创建归档对象
        NSKeyedArchiver* archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding: NO];
        
        //进行归档操作
        [archiver encodeObject: person1 forKey: @"personOne"];
        [archiver encodeObject: person2 forKey: @"personTwo"];
        
        //将归档(序列化)后的数据写入指定文件中
        [archiver.encodedData writeToFile: @"/Users/jakey/Desktop/CS/Xcode/NSKeyedArchiverTest/test.archiver" atomically: YES];
        
        //结束归档
        [archiver finishEncoding];
        
        
        //解档
        NSData* data = [NSData dataWithContentsOfFile: @"/Users/jakey/Desktop/CS/Xcode/NSKeyedArchiverTest/test.archiver"];
        NSKeyedUnarchiver* unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData: data error: nil];
        unarchiver.requiresSecureCoding = NO;
        
        Person* unchiverPerson1 = [unarchiver decodeObjectForKey: @"personOne"];
        NSLog(@"%@ %d %lf %@", unchiverPerson1.name, unchiverPerson1.age, unchiverPerson1.weight, unchiverPerson1.dog.name);
        Person* unchiverPerson2 = [unarchiver decodeObjectForKey: @"personTwo"];
        NSLog(@"%@ %d %lf %@", unchiverPerson2.name, unchiverPerson2.age, unchiverPerson2.weight, unchiverPerson2.dog.name);
    return 0;
}

运行结果如下:

请添加图片描述

解档 Success!!

注意

如果需要归档的类是某个自定义类的子类时,就需要在归档和解档之前实现父类的解档和归档方法:[super encodeWithCoder: coder];[super initWithCoder: coder];


MJExtension库(JSONModel、YYModel)

其实还可以使用MJExtension第三方库实现解归档,这样就可以不用写复杂的NSCoding协议,只需要一行代码调用写好的宏MJExtensionCodingImplementation就可以实现

MJExtension也和JSONModel、YYModel一样,支持 JSON数据<->Model 的转换同时也支持解归档,它们在代码量级上、性能优化上各有优缺点,详见这篇文章:

【YYModel,MJExtension,JSONModel对比】

具体的学习,小编日后了解!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/182349.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Java】认识String类

文章目录 一、String类的重要性二、String类中的常用方法1.字符串构造2.String对象的比较3.字符串查找4.转换5.字符串替换6.字符串拆分7.字符串截取8.其他操作方法9.字符串的不可变性10.字符串修改 三、StringBuilder和StringBuffer 一、String类的重要性 在C语言中已经涉及到…

微型计算机原理MOOC题

一、8254 1.掉坑了&#xff0c;AL传到端口不意味着一定传到的是低位&#xff0c;要看控制字D5和D4&#xff0c;10是只写高位&#xff0c;所以是0A00.。。 2. 3. 4.待解决&#xff1a;

leetCode 2925. 在树上执行操作以后得到的最大分数 + 正则难反 + 树形 DP

2925. 在树上执行操作以后得到的最大分数 - 力扣&#xff08;LeetCode&#xff09; 有一棵 n 个节点的无向树&#xff0c;节点编号为 0 到 n - 1 &#xff0c;根节点编号为 0 。给你一个长度为 n - 1 的二维整数数组 edges 表示这棵树&#xff0c;其中 edges[i] [ai, bi] 表示…

【从零开始实现意图识别】中文对话意图识别详解

前言 意图识别&#xff08;Intent Recognition&#xff09;是自然语言处理&#xff08;NLP&#xff09;中的一个重要任务&#xff0c;它旨在确定用户输入的语句中所表达的意图或目的。简单来说&#xff0c;意图识别就是对用户的话语进行语义理解&#xff0c;以便更好地回答用户…

JS数组常用的20种方法详解(每一个方法都有例子,超全面,超好理解的教程,干货满满)

目录 1.会改变原数组的方法&#xff08;7种&#xff09; 1.push() 2.pop() 3.unshift() 4.shift() 5.reverse() 6.sort() 7.splice() 2.不改变原数组的方法&#xff08;13种&#xff0c;返回的新数组是从原数组浅拷贝来的&#xff09; 1.concat() 2.join() 3.slice…

九、ffmpeg命令转封装

开了几天小差&#xff0c;今天继续学习ffmpeg。 准备测试使用的视频&#xff0c;并查看其信息 # 查看视频信息。使用Mediainfo也可以 ffprobe test.mp4 视频格式的信息如下。 保持编码格式&#xff1a;ffmpeg -i test.mp4 -vcodec copy -acodec copy test_copy.tsffmpeg -i…

2015年2月4日 Go生态洞察:Go语言中的包命名艺术

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

王道p150 20.将给定的表达式树转化为等价的中缀表达式(通过括号反应操作符的计算次序)

本题代码如下 void btreetoexp(tree t, char deep) {if (t NULL)return;else if (t->lchild NULL && t->rchild NULL)printf("%c", t->data);//输出操作数&#xff0c;不加括号else {if (deep > 1)printf("(");btreetoexp(t->l…

231123 刷题日报-动态规划

今天主要看了DP&#xff0c;前几天频繁遇到DP打击有点大。。 1. 0-1背包问题 要点&#xff1a; a. 三部曲&#xff1a; 1. 状态和选择 状态&#xff1a;物品序号、背包容量 选择&#xff1a;放、不放 2. dp数组定义、base case dp[i][w] 对于前i个物品&#xff0c;当前背包…

UNETR:用于三维医学图像分割的Transformer

论文链接&#xff1a;https://arxiv.org/abs/2103.10504 代码链接&#xff1a; https://monai.io/research/unetr 机构&#xff1a;Vanderbilt University, NVIDIA 最近琢磨不出来怎么把3d体数据和文本在cnn中融合&#xff0c;因为确实存在在2d里面用的transformer用在3d里面…

leetcode刷题之用栈实现队列(C语言版)

leetcode刷题之用栈实现队列&#xff08;C语言版&#xff09; 一、题目描述二、题目要求三、题目解析Ⅰ、typedef structⅡ、MyQueue* myQueueCreateⅢ、void myQueuePush(MyQueue* obj, int x)Ⅳ、int myQueuePeek(MyQueue* obj)Ⅴ、int myQueuePop(MyQueue* obj)Ⅶ、bool myQ…

编译器核心技术概览

编译技术是一门庞大的学科&#xff0c;我们无法对其做完善的讲解。但不同用途的编译器或编译技术的难度可能相差很大&#xff0c;对知识的掌握要求也会相差很多。如果你要实现诸如 C、JavaScript 这类通用用途语言&#xff08;general purpose language&#xff09;&#xff0c…

[shader] 光照入门(未完结。。。

反射 漫反射&#xff1a;而当物体表面粗糙时&#xff0c;我们把物体表面看作无数不同方向的微小镜面&#xff0c;则这些镜面反射出的光方向均不相同&#xff0c;这就是漫反射。 高光反射&#xff1a;我们假定物体表面光滑&#xff0c;只有一个镜面&#xff0c;那么所有的光都…

微信小程序前端环境搭建

搭建微信小程序前端环境 申请小程序测试账号 访问路径 使用微信扫描二维码进行申请&#xff0c;申请成功之后&#xff0c;进入界面&#xff0c;获取小程序ID(AppID)和秘钥(AppSecret) 安装微信web开发者工具 访问路径 选择稳定开发的版本 需要在小程序的设置中将默认关闭…

深入理解JVM 类加载机制

深入理解JVM 类加载机制 虚拟机如何加载Class文件&#xff1f; Class文件中的信息进入到虚拟机后会发生什么变化&#xff1f; 类加载机制就是Java虚拟机把描述类的数据从Class文件加载到内存&#xff0c;并对数据进行校验、转换解析和初始化&#xff0c;最终形成可以被虚拟机…

AMEYA360:瑞萨面向高端工业传感器系统推出高精度模拟前端的32位RX MCU

全球半导体解决方案供应商瑞萨电子&#xff08;TSE&#xff1a;6723&#xff09;宣布面向高端工业传感器系统推出一款全新RX产品——RX23E-B&#xff0c;扩展32位微控制器&#xff08;MCU&#xff09;产品线。新产品作为广受欢迎的RX产品家族的一员&#xff0c;具有高精度模拟前…

3D火山图绘制教程

一边学习&#xff0c;一边总结&#xff0c;一边分享&#xff01; 本期教程内容 **注&#xff1a;**本教程详细内容 Volcano3D绘制3D火山图 一、前言 火山图是做差异分析中最常用到的图形&#xff0c;在前面的推文中&#xff0c;我们也推出了好几期火山图的绘制教程&#xff0…

如何通过宝塔面板搭建一个本地MySQL数据库服务并实现远程访问

宝塔安装MySQL数据库&#xff0c;并内网穿透实现公网远程访问 文章目录 宝塔安装MySQL数据库&#xff0c;并内网穿透实现公网远程访问前言1.Mysql服务安装2.创建数据库3.安装cpolar3.2 创建HTTP隧道 4.远程连接5.固定TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网…

Axios 通过a标签下载文件 跨域下载

<!-- a标签占位 --><a ref"down" ></a>getTest() {this.$axios.request({url: https://cnv13.55.la/download?file_key3695fa9461a0ae59cf3148581e4fe339&handle_typeexcel2pdf,method: get,responseType: blob, // 切记类型 blob}).then(re…

java:CommandLineRunner命令行操作

背景 CommandLineRunner是一个SpringBoot提供的接口&#xff0c;这个接口可以让我们在SpringBoot启动之后&#xff0c;执行一些特定的命令行操作。 实现CommandLineRunner接口后&#xff0c;SpringBoot在启动的时候会自动执行run方法。通常&#xff0c;我们可以在run方法中进…