I'm trying to implement a class in Swift 2 for a single setting of any type that uses NSUserDefaults
under the covers.
Problem: How do I define a class for storing and retrieving any type of object, including Dictionary
?
I have a solution that works with AnyObject
which consists of a generic protocol (Settable
) and a generic class (Setting
). SettingsStore
is a wrapper around NSUserDefaults
.
// MARK: Settable Protocol
public protocol Settable {
typealias T
init(key: String, defaultValue: T, settingsStore: SettingsStore)
var value: T { get set }
func loadCurrentValue()
}
// MARK: Settings Class
public class Setting<T: AnyObject>: Settable {
private let key: String
private let defaultValue: T
private let settingsStore: SettingsStore
private var currentValue: T?
public required init(key: String, defaultValue: T, settingsStore: SettingsStore) {
self.key = key
self.defaultValue = defaultValue
self.settingsStore = settingsStore
}
public var value: T {
get {
if self.currentValue == nil {
self.loadCurrentValue()
}
return self.currentValue!
}
set {
self.currentValue = newValue
self.settingsStore.setObject(newValue.toAnyObject(), forKey: self.key)
}
}
public func loadCurrentValue() {
let optionalValue: T? = self.settingsStore.objectForKey(key) as? T
if let value = optionalValue {
self.currentValue = value
} else {
self.currentValue = self.defaultValue
}
}
}
This allows me to create a setting like this:
let specialId: Setting<String>
init() {
self.specialId = Setting<String>(
key: "specialId",
defaultValue: "<somevalue>",
settingsStore: self.settingsStore)
}
The problem with this is that it doesn't work with value types, such as String
, Bool
, Int
, Double
, Array
, or Dictionary
because they are all value types and value types don't conform to the AnyObject
protocol.
I've solved the problem for some of these using a protocol and extensions based on NSString
and NSNumber
, but a solution Dictionary
is proving to be elusive (I don't need a solution for Array
at the moment so I haven't spent time trying to solve that one).
// Change definition of Setting class like this:
public class Setting<T: AnyObjectRepresentable>: Settable {
...
}
public protocol AnyObjectRepresentable {
func toAnyObject() -> AnyObject
static func fromAnyObject(value: AnyObject) -> Self?
}
extension AnyObjectRepresentable where Self: AnyObject {
public func toAnyObject() -> AnyObject {
return self
}
public static func fromAnyObject(value: AnyObject) -> AnyObject? {
return value
}
}
extension String: AnyObjectRepresentable {
public func toAnyObject() -> AnyObject {
return NSString(string: self)
}
public static func fromAnyObject(value: AnyObject) -> String? {
let convertedValue = value as? String
return convertedValue
}
}
extension Bool: AnyObjectRepresentable {
public func toAnyObject() -> AnyObject {
return NSNumber(bool: self)
}
public static func fromAnyObject(value: AnyObject) -> Bool? {
let convertedValue = value as? Bool
return convertedValue
}
}
// Add extensions for Int and Double that look like the above extension for Bool.
I tried two different approaches for Dictionary
. The first one is similar to the String
approach:
extension Dictionary: AnyObjectRepresentable {
public func toAnyObject() -> AnyObject {
let value = self as NSDictionary
return value
}
public static func fromAnyObject(value: AnyObject) -> Dictionary? {
let convertedValue = value as? Dictionary
return convertedValue
}
}
Xcode gives me the following error on the first line of the toAnyObject()
method implementation:
'Dictionary' is not convertible to 'NSDictionary'
Next I tried extending NSDictionary
directly:
extension NSDictionary: AnyObjectRepresentable {
public func toAnyObject() -> AnyObject {
return NSDictionary(dictionary: self)
}
public static func fromAnyObject(value: AnyObject) -> NSDictionary? {
let convertedValue = value as? NSDictionary
return convertedValue
}
}
Xcode gives me the following error on the declaration of fromAnyObject()
:
Method 'fromAnyObject' in non-final class 'NSDictionary' must return Self
to conform to protocol 'AnyObjectRepresentable'
I'm at my wits. Is this solvable?
Thanks,
David
UPDATED 2015-09-15 16:30
For background, here is the definition and an implementation of SettingsStore
:
public protocol SettingsStore {
func objectForKey(key: String) -> AnyObject?
func setObject(value: AnyObject?, forKey key: String)
func dictionaryForKey(key: String) -> [String:AnyObject]?
}
public class UserDefaultsSettingsStore {
private let userDefaults: NSUserDefaults
public init() {
self.userDefaults = NSUserDefaults.standardUserDefaults()
}
public init(suiteName: String) {
self.userDefaults = NSUserDefaults(suiteName: suiteName)!
}
}
extension UserDefaultsSettingsStore: SettingsStore {
public func objectForKey(key: String) -> AnyObject? {
return self.userDefaults.objectForKey(key)
}
public func setObject(value: AnyObject?, forKey key: String) {
self.userDefaults.setObject(value, forKey: key)
self.userDefaults.synchronize()
}
public func dictionaryForKey(key: String) -> [String : AnyObject]? {
return self.userDefaults.dictionaryForKey(key)
}
}