Search code examples
swiftswift4settingsnsuserdefaultsimplicit-typing

Can you simultaneously define and instantiate implicit types in Swift?


Just messing around with the language thinking of how I want to structure some UserDefaults that automatically generate keys based on the hierarchy. That got me wondering... Is it possible to simultaneously define, and instantiate a type, like this?

let myUserSettings = {

    let formatting = {

        var lastUsedFormat:String

    }
}

let lastUsedFormat = myUserSettings.formatting.lastUsedFormat

Note: I can't use statics because I specifically need instancing so nested structs/classes with static members will not work for my case.

Here's the closest thing I could come up with, but I hate that I have to create initializers to set the members. I'm hoping for something a little less verbose.

class DefaultsScope {

    init(_ userDefaults:UserDefaults){
        self.userDefaults = userDefaults
    }

    let userDefaults:UserDefaults

    func keyForSelf(property:String = #function) -> String {
        return "\(String(reflecting: self)).\(property)"
    }
}

let sharedDefaults = SharedDefaults(UserDefaults(suiteName: "A")!)
class SharedDefaults : DefaultsScope {

    override init(_ userDefaults:UserDefaults){
        formatting = Formatting(userDefaults)
        misc       = Misc(userDefaults)
        super.init(userDefaults)
    }

    let formatting:Formatting
    class Formatting:DefaultsScope {

        let maxLastUsedFormats = 5

        fileprivate(set) var lastUsedFormats:[String]{
            get { return userDefaults.stringArray(forKey:keyForSelf()) ?? [] }
            set { userDefaults.set(newValue, forKey:keyForSelf()) }
        }

        func appendFormat(_ format:String) -> [String] {

            var updatedListOfFormats = Array<String>(lastUsedFormats.suffix(maxLastUsedFormats - 1))
            updatedListOfFormats.append(format)
            lastUsedFormats = updatedListOfFormats

            return updatedListOfFormats
        }
    }

    let misc:Misc
    class Misc:DefaultsScope {

        var someBool:Bool{
            get { return userDefaults.bool(forKey:keyForSelf()) }
            set { userDefaults.set(newValue, forKey:keyForSelf()) }
        }
    }
}

So is there a simpler way?


Solution

  • Ok, I think I've figured it out. This first class can go in some common library that you use for all your apps.

    class SettingsScopeBase {
    
        private init(){}
    
        static func getKey(setting:String = #function) -> String {
            return "\(String(reflecting:self)).\(setting)"
        }
    }
    

    The next part is a pair of classes:

    1. The 'Scoping' class where you define which user defaults instance to use (along with anything else you may want to specify for this particular settings instance)
    2. The actual hierarchy that defines your settings

    Here's the first. I'm setting this up for my shared settings between my application and it's extension:

    class SharedSettingsScope : SettingsScopeBase{
        static let defaults = UserDefaults(suiteName: "group.com.myco.myappgroup")!
    }
    

    And finally, here's how you 'set up' your hierarchy as well as how you implement the properties' bodies.

    class SharedSettings:SharedSettingsScope{
    
        class Formatting:SharedSettingsScope{
    
            static var groupsOnWhitespaceOnlyLines:Bool{
                get { return defaults.bool(forKey: getKey()) }
                set { defaults.set(newValue, forKey: getKey()) }
            }
        }
    }
    

    And here's how you use them...

    let x = SharedSettings.Formatting.groupsOnWhitespaceOnlyLines
    // x = false
    
    SharedSettings.Formatting.groupsOnWhitespaceOnlyLines = true
    
    let y = SharedSettings.Formatting.groupsOnWhitespaceOnlyLines
    // y = true
    

    I'm going to see if I can refine/optimize it a little more, but this is pretty close to where I want to be. No hard-coded strings, keys defined by the hierarchy where they're used, and only setting the specific UserDefaults instance in one place.