NSUserDefaultsController: “Attempt to set a non-pr

2019-08-20 07:32发布

问题:

I'm trying to use NSUserDefaultsController to implement a Preferences window in my Swift macOS app.

One of the settings that I need to persist is an array of presets, as defined by the following class:

class Preset: NSObject {
    var name = "abc"
    var value = 123

    override init() {
        super.init()
    }
}

Therefore, I need to persist a variable of type [Presets]. The visual representation in my Preferences window is an NSTableView, bound to an NSArrayController.

I followed this tutorial to set up my bindings. When running the app and trying to add a new preset (by clicking the + button), I get the following errors:

[User Defaults] Attempt to set a non-property-list object (...) as an NSUserDefaults/CFPreferences value for key presets
[General] Attempt to insert non-property list object (...) for key presets

I've tried implementing the Codable and NSCoding protocols, but the error persists.

From searching similar questions in Stack Overflow (such as this or this), it appears the solution would involve NSKeyedArchiver and NSKeyedUnarchiver. That appears simple enough to do if you're manually triggering loads and saves. Unfortunately I don't see how to use these classes along with NSUserDefaultsController.

回答1:

I solved the problem by subclassing NSArrayController as follows (see comment by Hamish to my other question, which was the last missing piece of the puzzle to make this generic):

extension Encodable {
    fileprivate func encode(to container: inout SingleValueEncodingContainer) throws {
        try container.encode(self)
    }
}

struct AnyEncodable: Encodable {
    var value: Encodable
    init(_ value: Encodable) {
        self.value = value
    }
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try value.encode(to: &container)
    }
}

class NSEncodableArrayController: NSArrayController {
    override func addObject(_ object: Any) {
        let data = try! PropertyListEncoder().encode(AnyEncodable(object as! Encodable))
        let any = try! PropertyListSerialization.propertyList(from: data, options: [], format: nil)

        super.addObject(any)
    }
}