1、解归档应用的对象:自定义对象
2、常规的解归档代码
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
//对自定义对象归档解档
@interface Student : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, assign) double height;
@end
NS_ASSUME_NONNULL_END
#import "Student.h"
//使用解归档对象的类都要遵守 NSCoding 协议,NSSecureCoding 协议继承自 NSCoding,有着比 NSCoding 更加安全的编码和解码。
@interface Student ()<NSSecureCoding>
@end
@implementation Student
/**归档:当一个对象归档(写入)沙盒时用*/
// 当将一个自定义对象保存到文件的时候就会调用该方法
-(void)encodeWithCoder:(NSCoder *)coder {
// 存储自定义对象的属性
NSLog(@"调用了 encodeWithCoder:方法");
[coder encodeObject:self.name forKey:@"name"];
[coder encodeInteger:self.age forKey:@"age"];
[coder encodeDouble:self.height forKey:@"height"];
}
//归档 - 从沙盒中读取对象时调用
-(instancetype)initWithCoder:(NSCoder *)coder {
NSLog(@"调用了initWithCoder:方法");
//注意:在构造方法中需要先初始化父类的方法
if (self = [super init]) {
self.name = [coder decodeObjectForKey:@"name"];
self.age = [coder decodeIntegerForKey:@"age"];
self.height = [coder decodeDoubleForKey:@"height"];
}
return self;
}
3、总结:常规的解归档方法对于自定义对象中的属性个数少是可以的,如果需要进行归解档的属性变得很多时,这种方式就不适用了, 这个时候就需要使用 runtime 来操作。
4、使用 runtime 自动解归档的思路及总结:
- 遵守 NSSecureCoding 协议
- 导入 #import <objc/runtime.h>
- 归档操作
- a) 获取所有实例变量
- b) 遍历所有实例变量
- c) 获取实例变量名
- d) 通过 KVC 获取 value
- e) 进行编码
- f) 手动管理内存
- 解档操作
- a) 获取所有实例变量
- b) 遍历所有实例变量
- c) 获取实例变量名
- d) 进行解码
- d) 通过 KVC 设置 value
- f) 手动管理内存
5、代码实现
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
//对自定义对象归档解档
//使用解归档对象的类都要遵守 NSCoding 协议,这里使用的 NSSecureCoding 协议继承自 NSCoding,有着比 NSCoding 更加安全的编码和解码。
@interface Student : NSObject<NSSecureCoding>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, assign) double height;
@end
NS_ASSUME_NONNULL_END
解归档
#import "Student.h"
#import <objc/runtime.h>
@implementation Student
// 归档的时候,系统会使用编码器把当前对象编码成二进制流
-(void)encodeWithCoder:(NSCoder *)coder {
unsigned int count = 0;
//获取所有实例变量
Ivar *ivars = class_copyIvarList([self class], &count);
//遍历所有实例变量
for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
//获取实例变量名
const char *name = ivar_getName(ivar);
//将获取到字符类型的实例变量名转换成 字符串类型
NSString *key = [NSString stringWithUTF8String:name];
//通过 KVC 获取 value
id value = [self valueForKey:key];
//进行编码
[coder encodeObject:value forKey:key];
}
// 因为是 C 语言的东西,不会自动释放,所以这里需要手动释放
free(ivars);
}
// 解档的时候,系统会把二进制流解码成对象
-(instancetype)initWithCoder:(NSCoder *)coder {
if (self = [super init]) {
unsigned int count = 0;
//获取所有实例变量
Ivar *ivars = class_copyIvarList([self class], &count);
//遍历
for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
//获取实例变量名
const char *name = ivar_getName(ivar);
NSString *key = [NSString stringWithUTF8String:name];
//原来的解码 decodeObjectForKey:方法 要换成decodeObjectOfClasses:方法
id value = [coder decodeObjectOfClasses:[NSSet setWithObject:[self class]] forKey:key];
//通过 KVC 获取 value
[self setValue:value forKey:key];
}
free(ivars);
}
return self;
}
//在使用 NSSecureCoding 的时候要实现supportsSecureCoding这个方法,返回 YES
+ (BOOL)supportsSecureCoding {
return YES;
}
使用的时候
//使用runtime自动解归档
//拼接存储自定义对象的文件
NSString *filePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"Student.plist"];
Student *s = [[Student alloc]init];
s.name = @"张三";
s.age = 21;
s.height = 168.5;
Student *s2;
if (@available(iOS 11.0, *)) {
//归档
NSError *error;
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:s requiringSecureCoding:YES error:&error];
if (error == nil) { //归档成功后,将数据写入文件中
[data writeToFile:filePath atomically:YES];
} else {
NSLog(@"获取数据失败!!!");
}
//解档
NSData *data2 = [[NSData alloc]initWithContentsOfFile:filePath];
s2 = [NSKeyedUnarchiver unarchivedObjectOfClass:[Student class] fromData:data2 error:nil];
} else {
//归档(存储数据)
[NSKeyedArchiver archiveRootObject:s toFile:filePath];
//接档(获取数据)
s = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
}
NSLog(@"name = %@, age = %ld, height = %f",s2.name, s2.age, s2.height);
打印
2022-08-03 17:01:11.926799+0800 Runtime 的相关应用场景[3248:2492131] name = 张三, age = 21, height = 168.500000