iOS Swift 4.2 Unarchive Array with NSKeyedUnarchiver unarchivedObject(ofClass:from:)

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<DecodedObjectType>(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 gat 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?

Replies

You've got a whole series of problems here.


— Swift has overlays onto the NSCoding APIs, so you normally don't need to cast anything as NSData. Use Data instead. (There still API that has NSMutableData, but you're not using that.)


— There is no concrete type called "Array", since it's a generic type. If you write "as? Array" you're asking the compiler to infer the generic parameter, so it might compile but it probably doesn't mean what you expect. There certainly isn't anything called "Array.self".


— The unarchive function from the overlay that you're using here requires secure coding. Therefore, you cannot just specify the class as "NSArray.self". Instead, you need to specify the class of the elements of the array too. See below.


— Never, never use "try?" syntax. If an error is thrown, you want to know what it is, not discard it. The extra level of optionality really confuses things, too. Even "try!" is pretty horrible, but at least it will display the error if it throws.


Here's a version of your code (with enough stuff added to allow it to compile) that runs in a playground:


class WidgetData: NSObject, NSSecureCoding {
    static var supportsSecureCoding: Bool {
        return true
    }
    override init() { }
    required init?(coder aDecoder: NSCoder) { }
    func encode(with aCoder: NSCoder) { }
    let x: String = "A"
    static func archiveWidgetDataArray(widgetDataArray : [WidgetData]) -> Data {
        return try! NSKeyedArchiver.archivedData(withRootObject: widgetDataArray as Array, requiringSecureCoding: false)
    }
    static func loadWidgetDataArray(unarchivedObject: Data) -> [WidgetData] {
        return try! NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSArray.self, WidgetData.self], from: unarchivedObject) as! [WidgetData]
    }
}

let w  = [WidgetData (), WidgetData ()]
let a = WidgetData.archiveWidgetDataArray(widgetDataArray: w)
let b = WidgetData.loadWidgetDataArray(unarchivedObject: a)
print(b)
  • thanks very much

Add a Comment