因为4月以后苹果要求不能使用老本版的Xcode
打包提审,所以最近一次上线更新升级成了Xcode 11.3.1
版本。iOS13适配要点总结有一些大佬已经总结很全面了,这里补充记录一个归档解档的坑。ios
- (void)updateCache { NSMutableDictionary *cache = [NSMutableDictionary dictionary]; if (self.viewModel.data1) { [cache setObject:self.viewModel.data1 forKey:@"data1"]; } if (self.viewModel.data2) { [cache setObject:self.viewModel.data2 forKey:@"data2"]; } if (self.viewModel.data3) { [cache setObject:self.viewModel.data3 forKey:@"data3"]; } NSData *archiverData = [NSKeyedArchiver archivedDataWithRootObject:[cache copy]]; NSString *archiverString = [archiverData base64EncodedStringWithOptions:0]; [[NSUserDefaults standardUserDefaults] setObject:archiverString forKey:@"cache"]; [[NSUserDefaults standardUserDefaults] synchronize]; } 复制代码
- (void)loadCache { NSString *archiverString = [[NSUserDefaults standardUserDefaults] objectForKey:@"cache"]; if (archiverString) { @try { NSData *archiverData = [[NSData alloc] initWithBase64EncodedString:archiverString options:0]; NSDictionary *cacheDic = [NSKeyedUnarchiver unarchiveObjectWithData:archiverData]; self.viewModel.data1 = cacheDic[@"data1"]; self.viewModel.data2 = cacheDic[@"data2"]; self.viewModel.data3 = cacheDic[@"data3"]; } @catch (NSException *exception) { [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"cache"]; } } } 复制代码
咱们的缓存策略是第一次进入页面返回数据后进行updateCache
操做,后续刷新接口时比对数据MD5
跟以前是否一致,不一导致用新数据展现并从新进行updateCache
,一致的话加载以前缓存数据loadCache
。macos
问题就是在loadCache
方法中解档出来的cacheDic
虽热归档进去的每一个对象都存在,可是对象对应的属性值所有都为nil
。缓存
寻找缘由很痛苦毕竟除了升级Xcode
其余什么都没改。最后在官方方法中看到了端倪。安全
+ (NSData *)archivedDataWithRootObject:(id)rootObject API_DEPRECATED("Use +archivedDataWithRootObject:requiringSecureCoding:error: instead", macosx(10.2,10.14), ios(2.0,12.0), watchos(2.0,5.0), tvos(9.0,12.0)); + (nullable id)unarchiveObjectWithData:(NSData *)data API_DEPRECATED("Use +unarchivedObjectOfClass:fromData:error: instead", macosx(10.2,10.14), ios(2.0,12.0), watchos(2.0,5.0), tvos(9.0,12.0)); 复制代码
iOS12
以后两个归档解档的方法被废弃了,iOS11
以后提供了新的方法。bash
+ (nullable NSData *)archivedDataWithRootObject:(id)object requiringSecureCoding:(BOOL)requiresSecureCoding error:(NSError **)error API_AVAILABLE(macos(10.13), ios(11.0), watchos(4.0), tvos(11.0));
+ (nullable id)unarchivedObjectOfClass:(Class)cls fromData:(NSData *)data error:(NSError **)error API_AVAILABLE(macos(10.13), ios(11.0), watchos(4.0), tvos(11.0)) NS_REFINED_FOR_SWIFT;
+ (nullable id)unarchivedObjectOfClasses:(NSSet<Class> *)classes fromData:(NSData *)data error:(NSError **)error API_AVAILABLE(macos(10.13), ios(11.0), watchos(4.0), tvos(11.0)) NS_REFINED_FOR_SWIFT;
复制代码
注意到官方新的API
中归档方法里面有个requiringSecureCoding
参数,对应归档数据是否遵循NSSecureCoding
协议。能够看出新的API
更加安全。markdown
- (void)updateCache { NSMutableDictionary *cache = [NSMutableDictionary dictionary]; if (self.viewModel.data1) { [cache setObject:self.viewModel.data1 forKey:@"data1"]; } if (self.viewModel.data2) { [cache setObject:self.viewModel.data2 forKey:@"data2"]; } if (self.viewModel.data3) { [cache setObject:self.viewModel.data3 forKey:@"data3"]; } NSData *archiverData = nil; if (@available(iOS 11.0, *)) { NSError *error = nil; archiverData = [NSKeyedArchiver archivedDataWithRootObject:[cache copy] requiringSecureCoding:YES error:&error]; } else { archiverData = [NSKeyedArchiver archivedDataWithRootObject:[cache copy]]; } NSString *archiverString = [archiverData base64EncodedStringWithOptions:0]; [[NSUserDefaults standardUserDefaults] setObject:archiverString forKey:@"cacheData"]; [[NSUserDefaults standardUserDefaults] synchronize]; } 复制代码
- (void)loadCache { NSString *archiverString = [[NSUserDefaults standardUserDefaults] objectForKey:@"cacheData"]; if (archiverString) { @try { NSData *archiverData = [[NSData alloc] initWithBase64EncodedString:archiverString options:0]; NSDictionary *cacheDic = nil; NSError *error = nil; if (@available(iOS 11.0, *)) { NSSet *set = [[NSSet alloc] initWithArray:@[[Data1Class class], [Data2Class class], [Data3Class class], [Data3Class class], [NSArray class], [NSDictionary class]]]; cacheDic = [NSKeyedUnarchiver unarchivedObjectOfClasses:set fromData:archiverData error:&error]; } else { cacheDic = [NSKeyedUnarchiver unarchiveObjectWithData:archiverData]; } self.viewModel.data1 = homeCacheDic[@"data1"]; self.viewModel.data2 = homeCacheDic[@"data2"]; self.viewModel.data3 = homeCacheDic[@"data3"]; } @catch (NSException *exception) { [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"cacheData"]; } } } 复制代码
最初的更改是直接替换了API
,发现无论requiringSecureCoding
设为true
或false
都仍是以前的效果(解档出来的每一个对象都存在,可是对象对应的属性值所有都为nil
)。数据结构
最终的解决方案是归档时requiringSecureCoding
设为true
,归档的自定义数据都遵照NSSecureCoding
协议,并实现对应方法。解档时unarchivedObjectOfClasses
对应的NSSet
要包括归档时数据结构的全部类名。app
NSSecureCoding
协议对应要实现的方法有3个:post
public protocol NSSecureCoding : NSCoding {
static var supportsSecureCoding: Bool { get }
}
public protocol NSCoding {
func encode(with coder: NSCoder)
init?(coder: NSCoder)
}
复制代码
因为咱们是OC
和Swift
混编,并使用了ObjectMapper
作数据模型转换。因此伪代码大概是这样:ui
import UIKit import ObjectMapper @objc(Data1) @objcMembers class Data1: NSObject, Mappable, NSSecureCoding { required init?(coder: NSCoder) { param1 = coder.decodeObject(forKey: "param1") as? String param2 = coder.decodeObject(forKey: "param2") as? String param3 = coder.decodeObject(forKey: "param3") as? String param4 = coder.decodeBool(forKey: "param4") } static var supportsSecureCoding: Bool { return true } func encode(with coder: NSCoder) { coder.encode(param1, forKey: "param1") coder.encode(param2, forKey: "param2") coder.encode(param3, forKey: "param3") coder.encode(param4, forKey: "param4") } var param1: String? var param2: String? var param3: String? var param4: Bool = false required init?(map: Map) { } func mapping(map: Map) { param1 <- map["param1"] param2 <- map["param2"] param3 <- map["param3"] param4 <- map["param4"] } } 复制代码
新API
在归档中用到的全部自定义数据模型类所有实现NSSecureCoding
以后,发现解档出来的对象对应的属性已经有正确的值了。
踩这个坑感受有几个点须要注意:
Key
须要更改一下,防止新代码读取老缓存失败的问题。NSArray
、NSDictionary
,那么在解档时unarchivedObjectOfClasses
对应的NSSet
中也应该添加对应的类名,不然解档出来的值为nil
。API
是iOS11
以后出的,因此要作好以前系统版本的兼容。unarchivedObjectOfClasses
对应的NSSet
中。