Search code examples
swiftappkit

NSCoder: how to encode/decode [Data] array


I'm having troubles encoding/decoding an array of Data (basically file bookmarks) in NSCoder protocol.

Here's what I'm trying to do:

class ClosedFiles: NSCoder {
    static var supportsSecureCoding = true
    let bookmarks: [Data]
    init?(with coder: NSCoder) {
        if let bm = coder.decodeObject() as? [Data] {
            bookmarks = bm
        } else {
            return nil
        }
    }
    init(bookmarks: [Data]) {
        self.bookmarks = bookmarks
    }
    
    func encode(with coder: NSCoder) {
        coder.encodeRootObject(bookmarks)
    }
}

I encode this object in my app delegate:

func application(_ app: NSApplication, willEncodeRestorableState coder: NSCoder) {
    coder.encodeRootObject(ClosedFiles(bookmarks: closedFiles))
}

This throws the error in runtime:

Ignoring exception: This decoder will only decode classes that adopt NSSecureCoding. Class '_TtCC7MyApp11AppDelegate11ClosedFiles' does not adopt it.

I'm following the apple's doc to conform to NSSecure, but it doesn't seem to work. I tried a few other method, but nothing seems working: coder.encode(), coder.encodeConditionalObject, and whatever else I found.

Any thought what I'm doing wrong?


Solution

  • You need a lot of changes for your class to support NSCoding (actually NSSecureCoding.

    • Your class must extend NSObject
    • Your class needs to conform to NSSecureCoding
    • You need the correct signature for the init(coder:) initializer
    • The supportsSecureCoding property needs to be static
    • The implementation of the two NSCoding methods need to be written correctly to support secure coding.

    Here's your class with all of the changes.

    class ClosedFiles: NSObject, NSSecureCoding {
        let bookmarks: [Data]
    
        required init?(coder: NSCoder) {
            if let bm = coder.decodeArrayOfObjects(ofClass: NSData.self, forKey: "bookmarks") as? [Data] {
                bookmarks = bm
            } else {
                return nil
            }
        }
    
        init(bookmarks: [Data]) {
            self.bookmarks = bookmarks
        }
    
        func encode(with coder: NSCoder) {
            coder.encode(bookmarks as [NSData], forKey: "bookmarks")
        }
    
        static var supportsSecureCoding: Bool {
            return true
        }
    }
    

    I tested these changes in a Playground using the following code:

    do {
        let closed = ClosedFiles(bookmarks: [ "Hello".data(using: .utf8)!, "There".data(using: .utf8)! ])
        let data = try NSKeyedArchiver.archivedData(withRootObject: closed, requiringSecureCoding: true)
        let dupe = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [ClosedFiles.self, NSArray.self, NSData.self], from: data) as! ClosedFiles
        let str1 = String(data: dupe.bookmarks[0], encoding: .utf8)!
        print(str1)
    } catch {
        print(error)
    }
    

    This gives back the expected results.