gfpacheco
gfpacheco

Reputation: 3215

Is there any way of sharing getter and setter through properties in Swift?

I'm building a helper to enable typed access to NSUserDefaults properties. Something like this:

struct UserDefaults {

  private static var standardUserDefaults: NSUserDefaults = {
    return NSUserDefaults.standardUserDefaults()
  }()

  private static let propKey = "PROP"
  static var prop: Bool {
    get {
      return standardUserDefaults.boolForKey(propKey)
    }
    set {
      standardUserDefaults.setBool(newValue, forKey: propKey)
      standardUserDefaults.synchronize()
    }
  }

}

This way I can have a nice syntax for reading and writing to NSUserDefaults:

UserDefaults.prop // read
UserDefaults.prop = false // write

The problem is that there's a lot of boilerplate code for this, I need 10 lines for each aditional property.

Is there any way of reducing the amount of lines needed for each new property? Reusing getter and setter? Any kind of run time generator?

Upvotes: 1

Views: 884

Answers (2)

Putz1103
Putz1103

Reputation: 6211

I like David's answer much better, but here's another option. Drops your 10 lines per variable down to 5 (mainly because of new line removal...)

struct UserDefaults {

    private static var standardUserDefaults: NSUserDefaults = {
        return NSUserDefaults.standardUserDefaults()
    }()

    //Repeate these 5 lines for all new variables, 
    //changing the as? to the proper variable type

    //Adding in a default value for the return in 
    //case the as? cast fails for any reason

    private static let propKey = "PROP"
    static var prop: Bool {
        get { return (getVar(propKey) as? Bool) ?? false }
        set { setVar(newValue, key:propKey) }
    }


    //The generic set/get
    private static func getVar(key : String) -> AnyObject?
    {
        return standardUserDefaults.objectForKey(key)
    }

    private static func setVar(newValue : AnyObject, key : String)
    {
        if(newValue is Bool)
        {
            standardUserDefaults.setBool((newValue as? Bool)!, forKey: key)
        }
            //... More cases here
        else if(newValue == nil)
        {
           standardUserDefaults.removeObjectForKey(key)
        }
        else
        {
            standardUserDefaults.setObject(newValue, forKey: key)
        }
        standardUserDefaults.synchronize()
    }
}

Upvotes: 0

David Berry
David Berry

Reputation: 41226

You can try wrapping the actual value in a class that handles all the dirty work for you:

class WrappedUserDefault<T> {
    let key : String
    let defaultValue : T

    var value : T {
        get {
            if let value = UserDefaults.standardUserDefaults.objectForKey(key) as? T {
                return value
            } else {
                return defaultValue
            }
        }
        set {
            if let value = newValue as? AnyObject {
                UserDefaults.standardUserDefaults.setValue(value, forKey: key)
            } else {
                UserDefaults.standardUserDefaults.removeObjectForKey(key)
            }
            UserDefaults.standardUserDefaults.synchronize()
        }
    }

    init(key:String, defaultValue:T) {
        self.key = key
        self.defaultValue = defaultValue
    }
}

struct UserDefaults {
    static let standardUserDefaults = NSUserDefaults.standardUserDefaults()

    static let ready = WrappedUserDefault<Bool>(key:"ready", defaultValue: true)
    static let count = WrappedUserDefault<Int>(key: "count", defaultValue: 0)
}

Then with just a little bit more code you wind up with:

UserDefaults.count.value++
UserDefaults.ready.value = true
UserDefaults.ready.value

If the verbosity of ready.value bothers you, you can somewhat hide that, although then you're back to you're back to having a fair amount of copy/paste code:

struct UserDefaults {
    static let standardUserDefaults = NSUserDefaults.standardUserDefaults()

    private static let readyWrapper = WrappedUserDefault<Bool>(key:"ready", defaultValue: true)
    static var ready : Bool { 
        get { return readyWrapper.value } 
        set { readyWrapper.value = newValue }
    }
}

At least in this case though, the copy/paste code is fairly trivial, so unlikely to need to be altered in the future.

Upvotes: 1

Related Questions