目标序列化
一、NSCoding
有时需求对自定义的类目标进行数据耐久化存储,可是NSUserDefaults
只能对系统的数据类型进行存储,而且还有存储推迟的问题,它实质便是一个plist
文件。自定义的plist
文件,一般都是保存配置或者是把一些不怎么需求变动的列表写进plist
里边,然后列表依据plist
的结构去读取,也完成不了想要的功用。而NSCoding
是使自定义目标可以被编码和解码以进行归档和分发的协议,可以完成存储的目的。
NSCoding协议声明晰一个类有必要完成的两个办法,这样该类的实例才能被编码和解码。这种功用为归档(目标和其他结构存储在磁盘上)和分发(目标被复制到不同的地址空间)供给了基础。依据面向目标的规划原则,被编码或解码的目标担任对其实例变量进行编码和解码。编码器经过调用
encodeWithCoder:
或initWithCoder:
来指示目标这样做。encodeWithCoder:
指示目标将其实例变量编码到所供给的编码器;目标可以接纳此办法任意次数。initWithCoder:
指示目标从供给的编码器中的数据初始化本身;因此,它取代了任何其他初始化办法,而且每个目标只发送一次。任何可编码的目标类都有必要选用NSCoding
协议并完成其办法。
定义一个用户类:
@interface User : NSObject<NSCoding>
@property (nonatomic, strong) NSString *registerName;
@property (nonatomic, strong) NSString *nickname;
@property (nonatomic, strong) NSString *phoneNumber;
@property (nonatomic, assign) BOOL isMember;
@property (nonatomic, assign) int balance;
@end
.m
文件进行归档编码操作:
@implementation User
- (void)encodeWithCoder:(nonnull NSCoder *)coder {
[coder encodeObject:_registerName forKey:@"registerName"];
[coder encodeObject:_nickname forKey:@"nickname"];
[coder encodeObject:_phoneNumber forKey:@"phoneNumber"];
[coder encodeBool:_isMember forKey:@"isMember"];
[coder encodeInt:_balance forKey:@"balance"];
}
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder {
if(self = [super init]){
if(coder){
_registerName = [coder decodeObjectOfClass:[NSString class] forKey:@"registerName"];
_nickname = [coder decodeObjectOfClass:[NSString class] forKey:@"nickname"];
_phoneNumber = [coder decodeObjectOfClass:[NSString class] forKey:@"phoneNumber"];
_isMember = [coder decodeBoolForKey:@"isMember"];
_balance = [coder decodeIntForKey:@"balance"];
}
}
return self;
}
@end
模拟器增加两个按钮,一个写入,一个读取:
点击把自定义数据存进本地:
- (IBAction)insertData:(UIButton *)sender {
User *user = [User new];
user.registerName = @"孙悟空";
user.nickname = @"山公";
user.phoneNumber = @"01234";
user.isMember = YES;
user.balance = 123;
User *user2 = [User new];
user2.registerName = @"庄周";
user2.nickname = @"鱼";
user2.phoneNumber = @"01245";
user2.isMember = YES;
user2.balance = 111;
NSArray <User *>*userArr = [NSArray arrayWithObjects:user,user2, nil];
NSData *perData = [NSKeyedArchiver archivedDataWithRootObject:userArr];
// 写入本地
NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"User.archiver"];
BOOL result = [[NSFileManager defaultManager]createFileAtPath:path contents:nil attributes:nil];
if (result){
[perData writeToFile:path atomically:YES];
}
}
点击把数写入本地的自定义数据取出来:
- (IBAction)getData:(UIButton *)sender {
NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"User.archiver"];
NSData *data = [NSData dataWithContentsOfFile:path];
NSArray <User *>*userArr = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSLog(@"userArr = %@",userArr);
for (User *user in userArr){
NSLog(@"\n - 注册称号:%@\n - 昵称:%@\n - 号码:%@\n - 是否会员:%d\n - 账户余额:%d", user.registerName,
user.nickname,
user.phoneNumber,
user.isMember,
user.balance
);
}
}
点击写入数据,运转打印:
NSCodingDemo[48440:1221308] 写入数据成功
点击读取数据:
NSCodingDemo[48440:1221308] userArr = (
"<User: 0x600002c89950>",
"<User: 0x600002c885a0>"
)
NSCodingDemo[48440:1221308]
- 注册称号:孙悟空
- 昵称:山公
- 号码:01234
- 是否会员:1
- 账户余额:123
NSCodingDemo[48440:1221308]
- 注册称号:庄周
- 昵称:鱼
- 号码:01245
- 是否会员:1
- 账户余额:111
二、抛弃提示
存档办法抛弃提示:
修正存档办法:
// NSData *perData = [NSKeyedArchiver archivedDataWithRootObject:userArr];
NSError *error = nil;
NSData *perData = [NSKeyedArchiver archivedDataWithRootObject:userArr requiringSecureCoding:YES error:&error];
解档办法抛弃提示:
修正抛弃提示:
// NSArray <User *>*userArr = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSError *error = nil;
NSArray <User *>*userArr = [NSKeyedUnarchiver unarchivedArrayOfObjectsOfClass:[User class] fromData:data error:&error];
从头运转:
NSCodingDemo[48863:1233999] 写入数据成功
NSCodingDemo[48863:1233999] userArr = (null)
没能读取数据:
研讨了一下是要改成
NSSecureCoding
,由于这种技术可能是不安全的,由于当您可以验证类类型时,目标现已结构好了,而且假如它是集合类的一部分,则可能插入到目标图中。为了契合NSSecureCoding
: 不重写initWithCoder:
的目标可以不做任何更改地契合NSSecureCoding
(假定它是另一个契合NSSecureCoding
的类的子类)。覆盖initWithCoder:
的目标有必要运用decodeObjectOfClass:forKey:
办法解码任何包括的目标。例如:id obj = [decoder decodeObjectOfClass:[MyClass类] forKey: @“myKey”);
此外,该类有必要重写其supportsSecureCoding
属性的getter
以回来YES
。
三、NSSecureCoding
一种可以以一种健壮的方式对目标替换进犯进行编码和解码的协议。
把User
遵从协议改成NSSecureCoding
:
@interface User : NSObject<NSSecureCoding>
增加支持安全编码:
+ (BOOL)supportsSecureCoding{
return YES;
}
运转测试:
userArr = (
"<User: 0x600003f6ebe0>",
"<User: 0x600003f6ea30>"
)
NSCodingDemo[54390:1372398]
- 注册称号:孙悟空
- 昵称:山公
- 号码:01234
- 是否会员:1
- 账户余额:123
NSCodingDemo[54390:1372398]
- 注册称号:庄周
- 昵称:鱼
- 号码:01245
- 是否会员:1
- 账户余额:111
四、YYCacheDemo适配问题
由于YYCache
很久不更新,在YYDiskCache
里边,- (id<NSSecureCoding>)objectForKey:(NSString *)key
办法里边,也是提示办法抛弃:
object = [NSKeyedUnarchiver unarchiveObjectWithData:item.value];
下面的- (void)setObject:(id<NSSecureCoding>)object forKey:(NSString *)key
办法里边,也是提示办法抛弃:
value = [NSKeyedArchiver archivedDataWithRootObject:object requiringSecureCoding:YES error:&error];
解档增加版本适配:
if (@available(iOS 12.0, *)) {
NSError *error = nil;
value = [NSKeyedArchiver archivedDataWithRootObject:object requiringSecureCoding:YES
error:&error];
} else {
// 消除办法弃用(过期)的正告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
// 要消除正告的代码
value = [NSKeyedArchiver archivedDataWithRootObject:object];
#pragma clang diagnostic pop
}
归档增加版本适配:
if (@available(iOS 12.0, *)) {
NSError *error = nil;
object = [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet
setWithArray:@[NSObject.class]] fromData:item.value error:&error];
}
else {
// 消除办法弃用(过期)的正告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
// 要消除正告的代码
object = [NSKeyedUnarchiver unarchiveObjectWithData:item.value];
#pragma clang diagnostic pop
}
把YYCache
里边一切的NSCoding
改成NSSecureCoding
,在自己自定义的类里边也增加支持安全编码:
+ (BOOL)supportsSecureCoding{
return YES;
}
运转测试,发现找不到自定义的类:
便是需求指定一个具体的类名,让他做解码操作,由于YYCache
是pod
下来的,不能直接导入文件,为了避免彼此引证,只用runtime
获取类:
object = [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithArray:@[objc_getClass("ContactsModel")]] fromData:item.value error:&error]
运转测试:
YYCacheDemo[57594:1453699] disk name = 张0 phoneNumber = 15888899990
YYCacheDemo[57594:1453699] disk name = 张1 phoneNumber = 15888899991
YYCacheDemo[57594:1453699] disk name = 张2 phoneNumber = 15888899992
YYCacheDemo[57594:1453699] disk name = 张3 phoneNumber = 15888899993
YYCacheDemo[57594:1453699] disk name = 张4 phoneNumber = 15888899994
YYCacheDemo[57594:1453699] disk name = 张5 phoneNumber = 15888899995
YYCacheDemo[57594:1453699] disk name = 张6 phoneNumber = 15888899996
YYCacheDemo[57594:1453699] disk name = 张7 phoneNumber = 15888899997
YYCacheDemo[57594:1453699] disk name = 张8 phoneNumber = 15888899998
YYCacheDemo[57594:1453699] disk name = 张9 phoneNumber = 15888899999
没有正告,运转正常。
五、优化代码
假如后边需求增加新的model
,就需求持续给NSKeyedUnarchiver unarchivedObjectOfClasses:
办法增加解档归档类名,需求经常修正原始框架代码,这样对于保护不利,由于从头增加一个类,专门处理增加解档归档model
类:
#import "YYModelSet.h"
#import <objc/runtime.h>
@implementation YYModelSet
+ (YYModelSet *)getClasses{
return (YYModelSet *)[NSSet setWithArray:@[objc_getClass("ContactsModel")]];
}
YYDiskCache
的改为:
object = [NSKeyedUnarchiver unarchivedObjectOfClasses:[YYModelSet getClasses] fromData:item.value error:&error];
这样,后边假如新增需求解档,归档的类,只需求修正自己新增YYModelSet
类办法即可,不动本来YYCache
的代码。