0

I'm having trouble archiving and/or unarchiving (not sure where the problem is, exactly) a set of custom classes from the iOS documents directory. The set is saved to disk (or at least it appears to be saved) because I can pull it from disk but I cannot unarchive it.

The model

final class BlockedUser: NSObject, NSSecureCoding {
    static var supportsSecureCoding = true
    let userId: String
    let name: String
    let date: Int
    var timeIntervalFormatted: String?

    init(userId: String, name: String, date: Int) {
        self.userId = userId
        self.name = name
        self.date = date
    }

    required convenience init?(coder: NSCoder) {
        guard let userId = coder.decodeObject(forKey: "userId") as? String,
            let name = coder.decodeObject(forKey: "name") as? String,
            let date = coder.decodeObject(forKey: "date") as? Int else {
                return nil
        }
        self.init(userId: userId, name: name, date: date)
    }

    func encode(with coder: NSCoder) {
        coder.encode(userId, forKey: "userId")
        coder.encode(name, forKey: "name")
        coder.encode(date, forKey: "date")
    }
}

Writing to disk

let fm = FileManager.default
let dox = fm.urls(for: .documentDirectory, in: .userDomainMask)[0]
let dir = dox.appendingPathComponent("master.properties", isDirectory: true)

do {
    let userData: [URL: Any] = [
        /* Everything else in this dictionary is a primitive type (string, bool, etc.)
           and reads and writes without problem from disk. The only thing I cannot
           get to work is the entry below (the set of custom classes). */
        dir.appendingPathComponent("blockedUsers", isDirectory: false): blockedUsers // of type Set<BlockedUser>
    ]

    for entry in userData {
        let data = try NSKeyedArchiver.archivedData(withRootObject: entry.value, requiringSecureCoding: true)
        try data.write(to: entry.key, options: [.atomic])
    }
} catch {
    print(error)
}

Reading from disk

if let onDisk = try? Data(contentsOf: dir.appendingPathComponent("blockedUsers", isDirectory: false)) {
    if let blockedUsers = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(onDisk) as? Set<BlockedUser> {
        print("success")
    } else {
        print("file found but cannot unarchive") // where I'm currently at
    }
} else {
    print("file not found")
}
lurning too koad
  • 2,698
  • 1
  • 17
  • 47
  • You don’t create the subdirectories to which you are trying to write – Eugene Dudnyk Sep 25 '20 at 00:14
  • Are you sure that the data is being written? – Itay Brenner Sep 25 '20 at 00:22
  • The subdirectories exist, that isn't the problem. They have to exist or else everything else in the dictionary would fail to read and write. And I can't be sure the data is written but it appears to be written. The problem appears to be unarchiving (or archiving, or both). – lurning too koad Sep 25 '20 at 01:01

1 Answers1

1

The problem is that you are trying to decode an object instead of decoding an integer. Check this post. Try like this:

class BlockedUser: NSObject, NSSecureCoding {
    static var supportsSecureCoding = true
    let userId, name: String
    let date: Int
    var timeIntervalFormatted: String?
    init(userId: String, name: String, date: Int) {
        self.userId = userId
        self.name = name
        self.date = date
    }
    func encode(with coder: NSCoder) {
        coder.encode(userId, forKey: "userId")
        coder.encode(name, forKey: "name")
        coder.encode(date, forKey: "date")
        coder.encode(timeIntervalFormatted, forKey: "timeIntervalFormatted")
    }
    required init?(coder: NSCoder) {
        userId = coder.decodeObject(forKey: "userId") as? String ?? ""
        name = coder.decodeObject(forKey: "name") as? String ?? ""
        date = coder.decodeInteger(forKey: "date")
        timeIntervalFormatted = coder.decodeObject(forKey: "timeIntervalFormatted") as? String
    }
}
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • What a strange API that calls strings objects and integers integers. You always come through Leo, you're one of my favorites. – lurning too koad Sep 25 '20 at 01:21
  • @acidgate But why are you using NSSecureCoding and archiving / dearchiving at all? All of this would be trivially easy if you would just adopt Codable instead. It seems to me that _you_ are the one who elected to go with the strange API belonging to Objective-C / Cocoa rather than the nice API that Swift gives you. – matt Sep 25 '20 at 01:51
  • @matt the dictionary that's being saved to disk has all sorts of different types in it and this approach allows me to use the same two lines of code to archive and write to disk whether it's a boolean or a custom class. This seems rather convenient. – lurning too koad Sep 25 '20 at 02:28