Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift 4: getter and setter directly in a struct or class

Tags:

I am trying to come up with a way to access UserDefaults using properties. However, I am stuck because I can't figure out how to make the properties dynamic, so to speak.

This is the generic idea, that API that I want:

class AppUserDefaults {
  var username = DefaultsKey<String>("username", defaultValue: "Unknown")
  var age = DefaultsKey<Int?>("age", defaultValue: nil)
}

let props = AppUserDefaults()
props.username = "bla"
print("username: \(props.username)")

But that (of course) doesn't work, since the type is DefaultsKey<String>, not String. So I have to add a value property to the DefaultsKey implementation just to add the getter and the setter, like this:

struct DefaultsKey<ValueType> {
  private let key: String
  private let defaultValue: ValueType

  public init(_ key: String, defaultValue: ValueType) {
    self.key = key
    self.defaultValue = defaultValue
  }

  var value: ValueType {
    get {
      let value = UserDefaults.standard.object(forKey: key)
      return value as? ValueType ?? defaultValue
    }
    set {
      UserDefaults.standard.setValue(newValue, forKey: key)
      UserDefaults.standard.synchronize()
    }
  }
}

and then use it like this:

let props = AppUserDefaults()
props.username.value = "bla"
print("username: \(props.username.value)")

But I find that rather ugly. I also tried a subscript method, but then you're still required to add [] instead of .value:

struct DefaultsKey<ValueType> {
  ...
  subscript() -> ValueType {
    get {
      let value = UserDefaults.standard.object(forKey: key)
      return value as? ValueType ?? defaultValue
    }
    set {
      UserDefaults.standard.setValue(newValue, forKey: key)
      UserDefaults.standard.synchronize()
    }
  }
}

let props = AppUserDefaults()
props.username[] = "bla"
print("username: \(props.username[])")

Basically what I want is that I can define the getter and the setter directly on DefaultsKey instead of having to go through that value property. Is this simply not possible with Swift? Is there another way to get the behaviour that I want, where properties defined on AppUserDefaults are "dynamic" and go through a getter and setter, without having to define it on the property declaration inside of AppUserDefaults?

I hope I am using the correct terms here and made the question clear for everyone.

like image 928
Kevin Renskers Avatar asked May 24 '18 11:05

Kevin Renskers


People also ask

Should I use getters and setters in Swift?

Getters and setters are essential in any language. Here, in Swift, they are applied to computed properties that do not have access to their own storage, so need to be derived from other properties.

Can you put a struct in a class Swift?

Classes within Swift are often seen as a blueprint for creating objects. With the ability to store values by defining properties and adding functionality through creating methods, classes and structs' shared features can often be used interchangeably in Swift.

Why do we use getter and setter methods in a class?

Getters and setters are used to protect your data, particularly when creating classes. For each instance variable, a getter method returns its value while a setter method sets or updates its value. Given this, getters and setters are also known as accessors and mutators, respectively.


1 Answers

The only thing I can think of is this:

struct DefaultsKey<ValueType> {
  private let key: String
  private let defaultValue: ValueType

  public init(_ key: String, defaultValue: ValueType) {
    self.key = key
    self.defaultValue = defaultValue
  }

  var value: ValueType {
    get {
      let value = UserDefaults.standard.object(forKey: key)
      return value as? ValueType ?? defaultValue
    }
    set {
      UserDefaults.standard.setValue(newValue, forKey: key)
      UserDefaults.standard.synchronize()
    }
  }
}

class AppUserDefaults {
  private var _username = DefaultsKey<String>("username", defaultValue: "Unknown")
  var username: String {
    set {
      _username.value = newValue
    },
    get {
      return _username.value
    }
  }
}

let props = AppUserDefaults()
props.username = "bla"
print("username: \(props.username)")
like image 186
Mihai Fratu Avatar answered Sep 24 '22 14:09

Mihai Fratu