Swift 4:getter和setter直接在struct或class中

时间:2018-05-24 11:00:55

标签: swift

我正试图想出一种使用属性访问UserDefaults的方法。但是,我陷入困境,因为我无法弄清楚如何使属性变得动态,可以这么说。

这是一般的想法,即我想要的API

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)")

但是(当然)不起作用,因为类型是DefaultsKey<String>,而不是String。所以我必须为DefaultsKey实现添加一个value属性,只是为了添加getter和setter,如下所示:

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()
    }
  }
}

然后像这样使用它:

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

但我觉得这很难看。我还尝试了下标方法,但是您仍然需要添加[]而不是.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[])")

基本上我想要的是我可以直接在DefaultsKey上定义getter和setter,而不必通过value属性。这对Swift来说根本不可能吗?是否有其他方法可以获得我想要的行为,AppUserDefaults上定义的属性是&#34;动态&#34;并通过getter和setter,而不必在AppUserDefaults内的属性声明中定义它?

我希望我在这里使用正确的术语,并为每个人提出明确的问题。

4 个答案:

答案 0 :(得分:4)

我唯一能想到的是:

 Comparator<T> c = new Comparator<T>() {
        @Override
        public int compare(T o1, T o2) {
            return 0;
        }
 };

答案 1 :(得分:2)

除了所有提议的变体外,您还可以定义自定义运算符,以便为DefaultsKey结构赋值。

为此,您的DefaultsKey结构应如下所示:

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

    public init(_ key: String, defaultValue: ValueType) {
        self.key = key
        self.defaultValue = defaultValue
    }
    public private(set) var value: ValueType {
        get {
            let value = UserDefaults.standard.object(forKey: key)
            return value as? ValueType ?? defaultValue
        }
        set {
            UserDefaults.standard.setValue(newValue, forKey: key)
        }
    }
}
  

DefaultsKey代码块的说明:

     
      
  1. private(set) var表示您只能在可以使用private访问级别访问此属性的位置设置此属性的值(您也可以写internal(set)fileprivate(set)能够相应地从internalfileprivate访问级别设置它。

         

    您需要稍后设置value属性。要访问此值,getter定义为public(在public之前写private(set))。

  2.   
  3. 您不需要使用synchronize()方法(“此方法是不必要的,不应使用”,参考:   https://developer.apple.com/documentation/foundation/userdefaults/1414005-synchronize)。

  4.   

现在是时候定义自定义操作符了(你可以根据需要命名,这只是举例):

infix operator <<<
extension DefaultsKey {
    static func <<<(left: inout DefaultsKey<ValueType>, right: ValueType) {
        left.value = right
    }
}

使用此运算符,您无法设置错误类型的值,因此它是类型安全的。

要测试你可以使用一点修改你的代码:

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

let props = AppUserDefaults()
props.username <<< "bla"
props.age <<< 21
props.username <<< "Yo man"
print("username: \(props.username.value)")
print("username: \(props.age.value)")

希望它有所帮助。

答案 2 :(得分:0)

radex.io中有一个really good blog post关于静态类型的NSUserDefaults,如果我理解你要做的事情,你可能会发现它很有用。

我已经为最新的Swift更新了它(因为我们现在有了条件一致性),并为我的实现添加了一个默认值,我已在下面列出。

class DefaultsKeys {}

class DefaultsKey<T>: DefaultsKeys {
    let key: String
    let defaultResult: T

    init (_ key: String, default defaultResult: T) {
        self.key = key
        self.defaultResult = defaultResult
    }
}

extension UserDefaults {
    subscript<T>(key: DefaultsKey<T>) -> T {
        get {
            return object(forKey: key.key) as? T ?? key.defaultResult
        }
        set {
            set(newValue, forKey: key.key)
        }
    }
}

// usage - setting up the keys
extension DefaultsKeys {
    static let baseCurrencyKey = DefaultsKey<String>("Base Currency", default: Locale.current.currencyCode!)
    static let archiveFrequencyKey = DefaultsKey<Int>("Archive Frequency", default: 30)
    static let usePasswordKey = DefaultsKey<Bool>("Use Password", default: false)
}

// usage - getting and setting a value
let uDefaults = UserDefaults.standard
uDefaults[.baseCurrencyKey] = "GBP"
let currency = uDefaults[.baseCurrencyKey]

答案 3 :(得分:0)

以下是我们在当前项目中的表现。它类似于其他一些答案,但我认为更少的锅炉板。以isFirstLaunch为例。

enum UserDafaultsKeys: String {
    case isFirstLaunch
}

extension UserDefaults {

    var isFirstLaunch: Bool {
        set {
            set(!newValue, forKey: UserDafaultsKeys.isFirstLaunch.rawValue)
            synchronize()
        }
        get { return !bool(forKey: UserDafaultsKeys.isFirstLaunch.rawValue) }
    }

}

然后像这样使用。

UserDefaults.standard.isFirstLaunch = true

它具有不需要学习单独的对象的好处,并且非常清楚存储变量的位置。