Search code examples
swiftgenericsdictionaryswift3swift-extensions

Swift 3 Generic Extension Arguments


In Swift 2.x, I had a nice little setup that allowed me to store and retrieve dictionary values using enum members:

public enum UserDefaultsKey : String {
    case mainWindowFrame
    case selectedTabIndex
    case recentSearches
}

extension Dictionary where Key : String {
    public subscript(key: UserDefaultsKey) -> Value? {
        get { return self[key.rawValue] }
        set { self[key.rawValue] = newValue }
    }
}

This allowed me to access values like this:

let dict = userDefaults.dictionaryForKey("SearchPrefs")
if let recentSearches = dict?[.recentSearches] as? [String] {
    // Populate "Recent" menu items
}

… instead of having to access values like this:

let dict = userDefaults.dictionaryForKey("SearchPrefs")
if let recentSearches = dict?[UserDefaultsKey.recentSearches.rawValue] as? [String] {
    // Populate "Recent" menu items
}

Note: The use of a string literal to access the dictionary from NSUserDefaults is for example purposes only. I wouldn't actually go out of my way to use an enum for dictionary keys, only to use a string literal to access the dictionary itself. :-)


Anyway, this has worked great for my needs, and it made reading and maintaining code involving NSUserDefaults a lot more pleasant.

Since migrating my project to Swift 3, however, I'm getting the following error:

extension Dictionary where Key: String {
    public subscript(key: UserDefaultsKey) -> Value? { <---- Use of undeclared type 'Value'
                                              ~~~~~~
        get {
            return self[key.rawValue]
        }
        set {
            self[key.rawValue] = newValue
        }
    }
}

I looked at the generated headers for Dictionary, and the generic Key and Value arguments are still present in the Generic Argument Clause of the Dictionary struct, so I'm not too sure what the issue is.

Do I need to rewrite the where clause to conform to some new Swift 3 grammar I'm unaware of? Or … can one no longer access generic placeholder types in extensions?

I just don't know what to do!

My project has only 28 migration errors left to resolve. I'm so close to actually getting to use Swift 3, so I'd love any pointers (as long as they're not Unsafe and/or Raw).

Thanks!


Solution

  • A generic parameter of a concrete type cannot be constrained to a concrete type, currently. This means that something like

    extension Dictionary where Key == String
    

    won't compile. It's a limitation of the generics system, and it hopefully won't be a problem in Swift 4.

    There is a workaround though, but it's a bit hacky:

    protocol StringConvertible {
        init(_ string: String)
    }
    
    extension String: StringConvertible {}
    
    extension Dictionary where Key: StringConvertible {
    
        subscript(key: UserDefaultsKey) -> Value? {
            get { return self[Key(key.rawValue)] }
            set { self[Key(key.rawValue)] = newValue }
        }
    
    }