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.

Leave a Reply