简体   繁体   中英

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?

Disclaimer: this is, probably, just an abstract solution that should not be used in real life:)

enum x {
  enum y {
    static func success() {
      print("Success")
    }
  }
}
x.y.success()

Update : Sorry, folks, I can't stop experimenting. This one looks pretty awful:)

let x2= [
  "y2": [
    "success": {
      print("Success")
    }
  ]
]
x2["y2"]?["success"]?()

Update 2 : One more try, this time with tuples. And since tuples must have at least two values, I had to add some dummies in there. Also, tuples cannot have mutating functions.

let x3 = (
  y3: (
    success: {
      print("Success")
    },
    failure: {
      print("Failure")
    }
  ),
  z3: 0
)
x3.y3.success()

You cannot have that kind of structure but you cant access y from inside x, since y is only visible inside the scope of x and so is success inside the scope of y. There is no way that you can access them from outside

One other alternative is to have higher order function like so, which return closure which is callable.

let x = {
            {
                {
                    print("Success")
                }
            }
        }

let y = x()
let success = y()
success()

or

x()()()

The real world usage of higher order function for userdefaults could be something like this,

typealias StringType = (String) -> ((String) -> Void)
typealias IntType = (String) -> ((Int) -> Void)
typealias BoolType = (String) -> ((Bool) -> Void)

typealias StringValue = (String) -> String?
typealias IntValue = (String) -> Int?
typealias BoolValue = (String) -> Bool?

func userDefaults<T>(_ defaults: UserDefaults) -> (String) -> ((T) ->  Void) {
    return { key in
        return { value in
            defaults.setValue(value, forKey: key)
        }
    }
}

func getDefaultsValue<T>(_ defaults: UserDefaults) -> (String) -> T? {
    return { key in
        return defaults.value(forKey: key) as? T
    }
}

let setStringDefaults: StringType  = userDefaults(.standard)
setStringDefaults("Name")("Jack Jones")
setStringDefaults("Address")("Australia")

let setIntDefaults: IntType = userDefaults(.standard)
setIntDefaults("Age")(35)
setIntDefaults("Salary")(2000)

let setBoolDefaults: BoolType = userDefaults(.standard)
setBoolDefaults("Married")(false)
setBoolDefaults("Employed")(true)

let getStringValue: StringValue = getDefaultsValue(.standard)
let name = getStringValue("Name")
let address = getStringValue("Address")

let getIntValue: IntValue = getDefaultsValue(.standard)
let age = getIntValue("Age")
let salary = getIntValue("Salary")

let getBoolValue: BoolValue = getDefaultsValue(.standard)
let married = getBoolValue("Married")
let employed = getBoolValue("Employed")

I am not sure if you like the pattern, but it has some good use cases as you can see from below, setStringDefaults you can set strings value to string key and all of them are typesafe.

You can extend this for your use case. But, you could use struct as well and use imperative code, which could be easier to understand. I see beauty in this as well.

How about you try nesting some swift structs ?

struct x {
    struct y {
        static func success() {
            print("success")
        }
    }
}

x.y.success()    

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM