因爲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
中。