目标序列化

一、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;
}

运转测试,发现找不到自定义的类:

对象序列化

便是需求指定一个具体的类名,让他做解码操作,由于YYCachepod下来的,不能直接导入文件,为了避免彼此引证,只用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的代码。