sunshinejr / SwiftyUserDefaults

Modern Swift API for NSUserDefaults
http://radex.io/swift/nsuserdefaults/static
MIT License
4.85k stars 366 forks source link

Dictionary with NSCoding based values #200

Open jompu opened 5 years ago

jompu commented 5 years ago

In the earlier 3.x version I had this:

class SomeClass: NSObject, NSCoding {
    var someVariable: String?

    init(bookIdentifier: String) {
        super.init()
        self.someVariable = someVariable
    }

    ///NSCoding
    func encode(with aCoder: NSCoder) {
        aCoder.encode(someVariable, forKey: "someVariable")
    }

    ///NSCoding
    required init?(coder aDecoder: NSCoder) {
        self.someVariable = aDecoder.decodeObject(forKey: "someVariable") as? String
    }
}
extension DefaultsKeys {
    static let someVariables = DefaultsKey<[String: SomeClass]>("someVariables")
}

extension UserDefaults {
    subscript(key: DefaultsKey<[String: SomeClass]>) -> [String: SomeClass] {
        get {
            if let someVariables =  unarchive(key) {
                return someVariables
            }
            else {
                return [:]
            }
        }
        set { archive(key, newValue) }
    }
}

According to migration guide it should be now just:

class SomeClass: NSObject, NSCoding, DefaultsSerializable {
    var someVariable: String?

    init(bookIdentifier: String) {
        super.init()
        self.someVariable = someVariable
    }

    ///NSCoding
    func encode(with aCoder: NSCoder) {
        aCoder.encode(someVariable, forKey: "someVariable")
    }

    ///NSCoding
    required init?(coder aDecoder: NSCoder) {
        self.someVariable = aDecoder.decodeObject(forKey: "someVariable") as? String
    }
}
extension DefaultsKeys {
    static let someVariables = DefaultsKey<[String: SomeClass]>("someVariables", defaultValue: [:])
}

But I'm getting following error:

[User Defaults] Attempt to set a non-property-list object {
    9789511310716 = "<AppName.SomeClass: 0x28085fc40>";
} as an NSUserDefaults/CFPreferences value for key someVariables
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to insert non-property list object {
    9789511310716 = "<AppName.SomeClass: 0x28085fc40>";
} for key someVariables'

So it doesn't seems to support dictionary with NSCoding values, even though you state that [String: Any] is supported.

jompu commented 5 years ago

I'm able to fix it for me with changing the code in DefaultsSerializable+BuiltIns.swift to:

extension Dictionary: DefaultsSerializable where Key == String {

    public typealias T = [Key: Value]

    public static var _defaults: DefaultsBridge<[Key: Value]> { return DefaultsKeyedArchiverBridge() }
    public static var _defaultsArray: DefaultsBridge<[[Key: Value]]> { return DefaultsArrayBridge() }
}

Shouldn't DefaultsKeyedArchiverBridge mean it works the same way as my custom subscript before? Is there way to get same effect without modifying SwiftyUserDefaults source code?

sunshinejr commented 5 years ago

Hey @jompu, great question. So initially we removed the dictionary support from 4.*, but we brought back the [String: String] and [String: Any] support for compatibility reasons. The thing is, the Any really means that we support [String: Any], but only if your type is a primitive that is accepted by the default UserDefaults methods. We do not yet provide support for the dictionary that use our DefaultsSerializable.

It could be improved to save/retrieve the values using the bridges we provide, but I don't really have time to add it to the library right now. I would gladly review PRs for that, though.

jompu commented 5 years ago

Thank you for your answer! I will consider to do it with bridges.