Question or problem with Swift language programming:
Since upgrading to Swift 4.2 I’ve found that many of the NSKeyedUnarchiver and NSKeyedArchiver methods have been deprecated and we must now use the type method static func unarchivedObject(ofClass: DecodedObjectType.Type, from: Data) -> DecodedObjectType? to unarchive data.
I have managed to successfully archive an Array of my bespoke class WidgetData, which is an NSObject subclass:
private static func archiveWidgetDataArray(widgetDataArray : [WidgetData]) -> NSData { guard let data = try? NSKeyedArchiver.archivedData(withRootObject: widgetDataArray as Array, requiringSecureCoding: false) as NSData else { fatalError("Can't encode data") } return data }
The problem comes when I try to unarchive this data:
static func loadWidgetDataArray() -> [WidgetData]? { if isKeyPresentInUserDefaults(key: USER_DEFAULTS_KEY_WIDGET_DATA) { if let unarchivedObject = UserDefaults.standard.object(forKey: USER_DEFAULTS_KEY_WIDGET_DATA) as? Data { //THIS FUNCTION HAS NOW BEEN DEPRECATED: //return NSKeyedUnarchiver.unarchiveObject(with: unarchivedObject as Data) as? [WidgetData] guard let nsArray = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSArray.self, from: unarchivedObject as Data) else { fatalError("loadWidgetDataArray - Can't encode data") } guard let array = nsArray as? Array else { fatalError("loadWidgetDataArray - Can't get Array") } return array } } return nil }
But this fails, as using Array.self instead of NSArray.self is disallowed. What am I doing wrong and how can I fix this to unarchive my Array?
How to solve the problem:
Solution 1:
You can use unarchiveTopLevelObjectWithData(_:)
to unarchive the data archived by archivedData(withRootObject:requiringSecureCoding:)
. (I believe this is not deprecated yet.)
But before showing some code, you should better:
-
Avoid using
NSData
, useData
instead -
Avoid using
try?
which disposes error info useful for debugging -
Remove all unneeded casts
Try this:
private static func archiveWidgetDataArray(widgetDataArray : [WidgetData]) -> Data { do { let data = try NSKeyedArchiver.archivedData(withRootObject: widgetDataArray, requiringSecureCoding: false) return data } catch { fatalError("Can't encode data: \(error)") } } static func loadWidgetDataArray() -> [WidgetData]? { guard isKeyPresentInUserDefaults(key: USER_DEFAULTS_KEY_WIDGET_DATA), //<- Do you really need this line? let unarchivedObject = UserDefaults.standard.data(forKey: USER_DEFAULTS_KEY_WIDGET_DATA) else { return nil } do { guard let array = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(unarchivedObject) as? [WidgetData] else { fatalError("loadWidgetDataArray - Can't get Array") } return array } catch { fatalError("loadWidgetDataArray - Can't encode data: \(error)") } }
But if you are making a new app, you should better consider using Codable
.
Solution 2:
unarchiveTopLevelObjectWithData(_:)
is deprecated as well. So to unarchive data without secure coding you need to:
- Create
NSKeyedUnarchiver
withinit(forReadingFrom: Data)
- Set
requiresSecureCoding
of created unarchiver to false. - Call
decodeObject(of: [AnyClass]?, forKey: String) -> Any?
to get your object, just use proper class andNSKeyedArchiveRootObjectKey
as key.
Solution 3:
if #available(iOS 12.0, *) { guard let unarchivedFavorites = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(favoritesData!) else { return } self.channelFavorites = unarchivedFavorites as! [ChannelFavorite] } else { if let unarchivedFavorites = NSKeyedUnarchiver.unarchiveObject(with: favoritesData!) as? [ChannelFavorite] { self.channelFavorites = unarchivedFavorites }
// Achieving data
if #available(iOS 12.0, *) { // use iOS 12-only feature do { let data = try NSKeyedArchiver.archivedData(withRootObject: channelFavorites, requiringSecureCoding: false) UserDefaults.standard.set(data, forKey: "channelFavorites") } catch { return } } else { // handle older versions let data = NSKeyedArchiver.archivedData(withRootObject: channelFavorites) UserDefaults.standard.set(data, forKey: "channelFavorites") }
This is the way I have updated my code and its working for me
Solution 4:
You are likely looking for this:
if let widgetsData = UserDefaults.standard.data(forKey: USER_DEFAULTS_KEY_WIDGET_DATA) { if let widgets = (try? NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSArray.self, WidgetData.self], from: widgetsData)) as? [WidgetData] { // your code } }
Solution 5:
Swift 5- IOS 13
guard let mainData = UserDefaults.standard.object(forKey: "eventDetail") as? NSData else { print(" data not found in UserDefaults") return } do { guard let finalArray = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(mainData as Data) as? [EventDetail] else { return } self.eventDetail = finalArray }