对象的归档
在某种情况下,我们可能有这样的需求,需要将程序中用到的多个对象及其属性值,以及它们的相互对应关系保存到文件中,或者发送到另外的进程。为了实现这个功能,Foundation框架中,可以把互相关联的多个对象归档为二进制文件,并且还可以将对象的关系从二进制文件内还原出来。像这样,将对象打包成二进制文件就称为归档。
Foundation框架的归档功能
将对象存储转换为二进制序列的过程称为归档、打包或编码,逆变换则称为解档或解码或对象还原。
Foundation框架中的归档和系统架构是相互独立的。即PowerPC 和Intel都可以利用。在对象包含的实例变量值中,整数及实数这样的基本数据类型,以及指向其他对象的指针等都可以归档。普通指针虽然不能归档,但是根据指向的数据类型有时也可以归档的。
可以使用 NSKeyedArchiver和 NSKeyedUnarchiver完成对象的归档和解档操作,而它们都是抽象类 NSCoder的子类。
所有可以归档的对象都必须要适用于NSCoding。协议NSCoding 在Foundation/NSObject.h 中定义,NSObject 自身并不采用该协议。NSString、NSDictionary 等Foundation 框架的主要类都适用协议NSCoding。
协议NSCoding 按照如下方式声明。
@protocol NSCoding
- (void)encodeWithCoder:(NSCoder *)coder;
- (nullable instancetype)initWithCoder:(NSCoder *)coder; // NS_DESIGNATED_INITIALIZER
@end
归档方法的定义
协议NSCoding 中,函数encodeWithCoder 定义了归档自身的方法。参数coder是传人的实例,NSKeyedArchiver的实例在 Foundation/NSKeyedArchiver.h 中。
- (void)encodeWithCoder:(NSCoder *)coder
{
// coder encodeObject:对象 forKey:关键词字符串
// coder encodeInteger:整数变量 forKey:关键词字符串
//可选数据类型有多种
[coder encodeObject:self.url forKey:@"url"];
}
如果超类适用于协议NSCoding,那么 super 将调用encodeWithCoder 对超类的实例变量进行归档。如果超类像NSObject 一样不适用于协议NSCoding,则不可调用。
解档方法的定义
为了从归档中还原对象,需要在各类定义初始化方法,即协议NSCoding 的方法initWithCoder。参数对象为coder,实际上是继承于NSCoder的NSKeyedArchiver实例变量。这个对象被称解档器。 NSKeyedUnarchiver 的接口在Foundation/NSKeyedArchiver.h中定义。
- (instancetype)initWithCoder:(NSCoder *)coder
{
//self=[super initWithCoder:coder]; 超类不适用于协议NSCoding,使用[super init]
self=[super init];
if(self){
//变量=[coder decodeObjectForKey:键值];
self.url=[coder decodeObjectForKey:@"url"];
}
return self;
}
只需在编码和解码中指定相同的键值,解码即可按照任意顺序还原对象,即使有不能还原的变量也无妨。
编码方法
//将字符串作为键值来对参数对象编码
- (void)encodeObject:(nullable id)object forKey:(NSString *)key;
//在对象图中需要参数object时将字符串作为键值编码
- (void)encodeConditionalObject:(nullable id)object forKey:(NSString *)key;
//将字符串作为键值对整数参数归档
- (void)encodeInt:(int)value forKey:(NSString *)key;
解码方法
//使用指定键值来还原编码对象
- (nullable id)decodeObjectForKey:(NSString *)key;
//将键值为参数字符串的整数解码
- (int)decodeIntForKey:(NSString *)key;
归档和解档初始化方法
NSKeyedArchiver 的实例,即归档器。可将对象编码结果写入数据对象内。NSKeyedArchiver 的初始化方法为:
//将预先生成的NSMutableData 实例作为参数,初始化NSKeyedArchiver 实例。参数的数据对象被保存下来。最终生成归档。
- (instancetype)initForWritingWithMutableData:(NSMutableData *)data;
归档器生成后,就可使用上面介绍的encode...方法进行数据归档。编码根对象后,对象图全体会被递归地进行归档。
所有归档完成后,最后必须调用finishEncoding 方法进行后处理。
- (void)finishEncoding;
处理完成后,因归档器初始化传人的对象已经进行了归档。即可将其保存到文件中,或向其他进程发送消息。
解档器NSKeyedUnarchiver的初始化方法
//初始化接收器,将参数对象从归档中还原,参数data被保存在接收器内
- (nullable instancetype)initForReadingFromData:(NSData *)data error:(NSError **)error;
还原归档的对象图和还原一个对象是同样的,都使用方法decodeObjectForKey:。
以上介绍了归档和解档实例生成方法,还有更简洁的方法也可将归档结果写入文件内,进行读取与还原。
//NSKeyedArchiver 类中的方法,通过指定根对象将归档结果写入指定路径的文件中。返回值为YES表示成功
+ (BOOL)archiveRootObject:(id)rootObject toFile:(NSString *)path;
//NSKeyedUnarchiver 类中方法,从指定路径中读出归档数据进行解档,返回根对象。处理失败则返回nil。
+ (nullable id)unarchiveObjectWithFile:(NSString *)path;
举例说明
@interface ProductModel : NSObject<NSCoding>
@property (nonatomic,copy)NSString *url;
@end
#import "ProductModel.h"
@implementation ProductModel
- (instancetype)initWithCoder:(NSCoder *)coder
{
//self=[super initWithCoder:coder]; 超类不适用于协议NSCoding,使用[super init]
self=[super init];
if(self){
self.url=[coder decodeObjectForKey:@"url"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:self.url forKey:@"url"];
}
@end
- (void)createProducts{
ProductModel *product=[[ProductModel alloc]init];
product.url=@"https://www.baidu.com/";
NSString*cachePath =NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask,YES).firstObject;
NSString *path=[cachePath stringByAppendingPathComponent:@"product"];
NSLog(@"path:%@",path);
[NSKeyedArchiver archiveRootObject:product toFile:path];
id object=[NSKeyedUnarchiver unarchiveObjectWithFile:path];
ProductModel *productModel=object;
NSLog(@"productModel.url:%@",productModel.url);
}
//打印日志为:
2021-12-29 16:09:28.443771+0800 OCTest3[496:38974] path:/var/mobile/Containers/Data/Application/E3E1CDF0-AF9D-4D0B-9425-806439F2AA0C/Library/Caches/product
2021-12-29 16:09:28.463214+0800 OCTest3[496:38974] productModel.url:https://www.baidu.com/