UserDefaults with generic keys


(Thierry Passeron) #1

Hi Everyone,

Using Swift 3.1, I was wondering if I could come up with something largely inspired by Notification.Name to help me deal with UserDefaults so I started by doing something like:

public struct DefaultsKey: RawRepresentable, Equatable, Hashable, Comparable {
  
  public var rawValue: String
  public var hashValue: Int { return rawValue.hash }
  
  public init(_ rawValue: String) { self.rawValue = rawValue }
  public init(rawValue: String) { self.rawValue = rawValue }
  
  /* Protocols implementation .. */
}

Now I can make extensions like:

extension DefaultsKey {
  static let version = DefaultsKey("version »)
}

And use it to query the UserDefaults.

public func Defaults<T>(_ key: DefaultsKey) -> T? {
  return UserDefaults.standard.object(forKey: key.rawValue) as? T
}

let version: String? = Defaults(.version)

Nice, concise, I love it…

But It could be even better to let the compiler check the return type of the UserDefault for the DefaultKey that I ask if only I could create the key and bind it to a type. So I tried this:

public struct DefaultsKey<T>: RawRepresentable, Equatable, Hashable, Comparable {

}

extension DefaultsKey {
  static let version = DefaultsKey<String>("version »)
}

But this doesn’t compile:
   error: static stored properties not supported in generic types

I guess I could keep all the keys outside an extension scope but then it would not be as concise as with Notification.Name

Please let me know if there is indeed a generic way to solve this. Any help would be greatly appreciated.
Thanks in advance.

Thierry.


(Vladimir) #2

Hi Everyone,

Using Swift 3.1, I was wondering if I could come up with something largely inspired by Notification.Name to help me deal with UserDefaults so I started by doing something like:

The only kind of solution I was able to implement, is with calculated properties in extension. Hope this have any sense and most likely can be improved(code from swift sandbox):

struct DefaultsKey<T> {
  var rawValue : String
  
  init(_ name: String) {
    rawValue = name
  }
}

extension DefaultsKey {
  static var version : DefaultsKey<String> { return DefaultsKey<String>("version") }
  static var code : DefaultsKey<Int> { return DefaultsKey<Int>("code") }
}

func UserDefaults_standard_object(forKey: String) -> Any? {
  switch forKey {
    case "version" : return "1.0.0"
    case "code" : return 12345
    default : return nil
  }
}

func Defaults<T>(_ key: DefaultsKey<T>) -> T? {
   return UserDefaults_standard_object(forKey: key.rawValue) as? T
}

let version = Defaults(.version)
let code = Defaults(.code)

print(version ?? "-no value-", type(of: version)) // 1.0.0 Optional<String>
print(code ?? "-no value-", type(of: code)) // 12345 Optional<Int>

···

On 07.07.2017 14:02, Thierry Passeron via swift-users wrote:

public struct DefaultsKey: RawRepresentable, Equatable, Hashable, Comparable {
      public var rawValue: String
   public var hashValue: Int { return rawValue.hash }
      public init(_ rawValue: String) { self.rawValue = rawValue }
   public init(rawValue: String) { self.rawValue = rawValue }
      /* Protocols implementation .. */
}

Now I can make extensions like:

extension DefaultsKey {
   static let version = DefaultsKey("version »)
}

And use it to query the UserDefaults.

public func Defaults<T>(_ key: DefaultsKey) -> T? {
   return UserDefaults.standard.object(forKey: key.rawValue) as? T
}

let version: String? = Defaults(.version)

Nice, concise, I love it…

But It could be even better to let the compiler check the return type of the UserDefault for the DefaultKey that I ask if only I could create the key and bind it to a type. So I tried this:

public struct DefaultsKey<T>: RawRepresentable, Equatable, Hashable, Comparable {

}

extension DefaultsKey {
   static let version = DefaultsKey<String>("version »)
}

But this doesn’t compile:
    error: static stored properties not supported in generic types

I guess I could keep all the keys outside an extension scope but then it would not be as concise as with Notification.Name

Please let me know if there is indeed a generic way to solve this. Any help would be greatly appreciated.
Thanks in advance.

Thierry.
_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Kim Burge Strand) #3

Here's yet another alternative. I read an article doing this very thing a
while back, it might be interesting to you:
http://radex.io/swift/nsuserdefaults/static/. It makes the key type a class
instead, and inherits from a non-generic parent class to which it adds the
static properties.

The gist of it is roughly like this (although the article uses subscript,
which does not allow for a generic implementation so I use a get/set
approach here for brevity):

class DefaultsKeys {}
final class DefaultsKey<T>: DefaultsKeys {
    let value: String

    init(_ value: String) {
        self.value = value
    }
}

extension UserDefaults {
    func get<T>(_ key: DefaultsKey<T>) -> T? {
        return object(forKey: key.value) as? T
    }

    func set<T>(_ key: DefaultsKey<T>, to value: T) {
        set(value, forKey: key.value)
    }
}

extension DefaultsKeys {
    static let version = DefaultsKey<String>("version")
}

let defaults = UserDefaults.standard
defaults.set(.version, to: "1.0")
let version = defaults.get(.version)
print(version ?? "N/A")
···

On Fri, 7 Jul 2017 at 14:00 Vladimir.S via swift-users < swift-users@swift.org> wrote:

On 07.07.2017 14:02, Thierry Passeron via swift-users wrote:
> Hi Everyone,
>
> Using Swift 3.1, I was wondering if I could come up with something
largely inspired by Notification.Name to help me deal with UserDefaults so
I started by doing something like:

The only kind of solution I was able to implement, is with calculated
properties in
extension. Hope this have any sense and most likely can be improved(code
from swift
sandbox):

struct DefaultsKey<T> {
        var rawValue : String

        init(_ name: String) {
                rawValue = name
        }
}

extension DefaultsKey {
        static var version : DefaultsKey<String> { return
DefaultsKey<String>("version") }
        static var code : DefaultsKey<Int> { return
DefaultsKey<Int>("code") }
}

func UserDefaults_standard_object(forKey: String) -> Any? {
        switch forKey {
                case "version" : return "1.0.0"
                case "code" : return 12345
                default : return nil
        }
}

func Defaults<T>(_ key: DefaultsKey<T>) -> T? {
   return UserDefaults_standard_object(forKey: key.rawValue) as? T
}

let version = Defaults(.version)
let code = Defaults(.code)

print(version ?? "-no value-", type(of: version)) // 1.0.0 Optional<String>
print(code ?? "-no value-", type(of: code)) // 12345 Optional<Int>

>
> public struct DefaultsKey: RawRepresentable, Equatable, Hashable,
Comparable {
>
> public var rawValue: String
> public var hashValue: Int { return rawValue.hash }
>
> public init(_ rawValue: String) { self.rawValue = rawValue }
> public init(rawValue: String) { self.rawValue = rawValue }
>
> /* Protocols implementation .. */
> }
>
> Now I can make extensions like:
>
> extension DefaultsKey {
> static let version = DefaultsKey("version »)
> }
>
> And use it to query the UserDefaults.
>
> public func Defaults<T>(_ key: DefaultsKey) -> T? {
> return UserDefaults.standard.object(forKey: key.rawValue) as? T
> }
>
> let version: String? = Defaults(.version)
>
> Nice, concise, I love it…
>
> But It could be even better to let the compiler check the return type of
the UserDefault for the DefaultKey that I ask if only I could create the
key and bind it to a type. So I tried this:
>
> public struct DefaultsKey<T>: RawRepresentable, Equatable, Hashable,
Comparable {
> …
> }
>
> extension DefaultsKey {
> static let version = DefaultsKey<String>("version »)
> }
>
> But this doesn’t compile:
> error: static stored properties not supported in generic types
>
> I guess I could keep all the keys outside an extension scope but then it
would not be as concise as with Notification.Name
>
> Please let me know if there is indeed a generic way to solve this. Any
help would be greatly appreciated.
> Thanks in advance.
>
> Thierry.
> _______________________________________________
> swift-users mailing list
> swift-users@swift.org
> https://lists.swift.org/mailman/listinfo/swift-users
>
_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Thierry Passeron) #4

Nice!

The key is to have a non generic base class as storage for statics and a subclass for generic types… brilliant.
I am definitely going to try this one.

Vladimir’s solution is also a nice fact to know. The key point of the compile error message seems to be « static __stored__ « hence, with computed it works.

Thanks for the posting.

···

Le 7 juil. 2017 à 15:54, Kim Burgestrand <kim@burgestrand.se> a écrit :

Here's yet another alternative. I read an article doing this very thing a while back, it might be interesting to you: http://radex.io/swift/nsuserdefaults/static/. It makes the key type a class instead, and inherits from a non-generic parent class to which it adds the static properties.

The gist of it is roughly like this (although the article uses subscript, which does not allow for a generic implementation so I use a get/set approach here for brevity):

class DefaultsKeys {}
final class DefaultsKey<T>: DefaultsKeys {
    let value: String

    init(_ value: String) {
        self.value = value
    }
}

extension UserDefaults {
    func get<T>(_ key: DefaultsKey<T>) -> T? {
        return object(forKey: key.value) as? T
    }

    func set<T>(_ key: DefaultsKey<T>, to value: T) {
        set(value, forKey: key.value)
    }
}

extension DefaultsKeys {
    static let version = DefaultsKey<String>("version")
}

let defaults = UserDefaults.standard
defaults.set(.version, to: "1.0")
let version = defaults.get(.version)
print(version ?? "N/A")

On Fri, 7 Jul 2017 at 14:00 Vladimir.S via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:
On 07.07.2017 14:02, Thierry Passeron via swift-users wrote:
> Hi Everyone,
>
> Using Swift 3.1, I was wondering if I could come up with something largely inspired by Notification.Name to help me deal with UserDefaults so I started by doing something like:

The only kind of solution I was able to implement, is with calculated properties in
extension. Hope this have any sense and most likely can be improved(code from swift
sandbox):

struct DefaultsKey<T> {
        var rawValue : String

        init(_ name: String) {
                rawValue = name
        }
}

extension DefaultsKey {
        static var version : DefaultsKey<String> { return DefaultsKey<String>("version") }
        static var code : DefaultsKey<Int> { return DefaultsKey<Int>("code") }
}

func UserDefaults_standard_object(forKey: String) -> Any? {
        switch forKey {
                case "version" : return "1.0.0"
                case "code" : return 12345
                default : return nil
        }
}

func Defaults<T>(_ key: DefaultsKey<T>) -> T? {
   return UserDefaults_standard_object(forKey: key.rawValue) as? T
}

let version = Defaults(.version)
let code = Defaults(.code)

print(version ?? "-no value-", type(of: version)) // 1.0.0 Optional<String>
print(code ?? "-no value-", type(of: code)) // 12345 Optional<Int>

>
> public struct DefaultsKey: RawRepresentable, Equatable, Hashable, Comparable {
>
> public var rawValue: String
> public var hashValue: Int { return rawValue.hash }
>
> public init(_ rawValue: String) { self.rawValue = rawValue }
> public init(rawValue: String) { self.rawValue = rawValue }
>
> /* Protocols implementation .. */
> }
>
> Now I can make extensions like:
>
> extension DefaultsKey {
> static let version = DefaultsKey("version »)
> }
>
> And use it to query the UserDefaults.
>
> public func Defaults<T>(_ key: DefaultsKey) -> T? {
> return UserDefaults.standard.object(forKey: key.rawValue) as? T
> }
>
> let version: String? = Defaults(.version)
>
> Nice, concise, I love it…
>
> But It could be even better to let the compiler check the return type of the UserDefault for the DefaultKey that I ask if only I could create the key and bind it to a type. So I tried this:
>
> public struct DefaultsKey<T>: RawRepresentable, Equatable, Hashable, Comparable {
> …
> }
>
> extension DefaultsKey {
> static let version = DefaultsKey<String>("version »)
> }
>
> But this doesn’t compile:
> error: static stored properties not supported in generic types
>
> I guess I could keep all the keys outside an extension scope but then it would not be as concise as with Notification.Name
>
> Please let me know if there is indeed a generic way to solve this. Any help would be greatly appreciated.
> Thanks in advance.
>
> Thierry.
> _______________________________________________
> swift-users mailing list
> swift-users@swift.org <mailto:swift-users@swift.org>
> https://lists.swift.org/mailman/listinfo/swift-users
>
_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users