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

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

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

目录


简介

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

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

归档一般保存自定义对象、自定义对象数组,由于自定义对象不具有归档的性质,所以只有遵循了NSCoding协议的类才可以归档

由于决大多数支持存储数据的Foundation和Cocoa Touch类都遵循了NSCoding协议,因此,对于大多数OC提供的类来说,归档相对而言还是比较容易实现的。

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

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

iOS 13中需要支持NSSecureCoding 协议(父协议为NSCoding)才能支持归档

  1. 自定义一个Person类并实现NSCoding 协议的方法
objectivec 复制代码
@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;

objectivec 复制代码
        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;

objectivec 复制代码
        //解档此二进制数据
        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. 初始化待归档对象并进行归档
objectivec 复制代码
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. 依次解档
objectivec 复制代码
//解档
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类要手动添加协议函数:

objectivec 复制代码
@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;
}

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

objectivec 复制代码
#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对比】

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

相关推荐
酷虎软件16 小时前
分享链接+视频音频文案提取 API 接口文档
ide·macos·xcode
Hyyy20 小时前
为什么 macOS 应用一换 Bundle ID,之前授予的权限就全失效了?
macos·electron
CocoaKier21 小时前
Facebook登录的这条红色警告,我终于搞明白了,真是反人类设计!
ios·facebook
p似笑非笑21 小时前
实战验证——把 SDK 塞进一个 macOS 原生 Agent 应用
macos
humors2211 天前
【转帖】安全企业发布iOS漏洞攻击风险检测工具
安全·ios·手机·苹果
p似笑非笑1 天前
在Mac上完美配置VSCode的C/C++开发环境(GCC/G++详细教程)
c语言·vscode·macos
沐风___1 天前
iOS 崩溃收集实战:从「只看到次数、看不到原因」到 Sentry 完整集成
ios·sentry
listening7771 天前
HarmonyOS 6.1 跨端实战:用ArkUI-X把电商App同时跑在Android/iOS上
android·ios·harmonyos
韦胖漫谈IT1 天前
Apple M3 Max 与 Apple M5 Max 对比:本地算力的新旧王者之争
网络·人工智能·macos·transformer
Tyler_11 天前
iOS PlayWright mcp server
前端·ios·ai编程