2

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)
    }
}
David Potter
  • 2,272
  • 2
  • 22
  • 35

1 Answers1

0

If you substitute AnyObject with Any, I think you'll get the results you're looking for. Specifically, replace this line:

public class Setting<T: AnyObject>: Settable {

with this line

public class Setting<T: Any>: Settable {
Jason Barker
  • 3,020
  • 1
  • 17
  • 11
  • I tried that but then I ran into a problem with SettingsStore, which is used to read and write the setting from/to NSUserDefaults. Any ideas? – David Potter Sep 15 '15 at 23:52