背景
开发过程正中可爱的产品总会给我们提一个这样的需求
“这个页面用户第一次进入的时候弹个框显示一下我们的用户协议,点完确认之后不再显示。”
收到这样的需求脑子里第一个冒出的想法就是“简单,用在UserDefaults里放个key,每次进来的时候取一下就好了,有就代表显示过了,没有就代表没显示过”
let value = UserDefaults.standard.object(forKey: "key1") as? Bool ?? false
if !value {
// do something
// ......
// ......
UserDefaults.standard.set(true, forKey: "key1")
UserDefaults.standard.synchronize()
}
然后过些日子产品又来这个地方再加个“使用规则”
于是上面的代码换了个key复制了
然后再过些日子...,于是就出现了“key2”,“key3”,“key4”...上面的代码写得到处都是。等你回头来review的时候发现...@propertyWrapper
这个时候就可以用@propertyWrapper属性包装器来解决
@propertyWrapper // 标记这个struct是个属性包装器
struct UserDefaultsWrapper<T> {
private let key: String
private let defaultValue: T
// 这个实例方法根据业务情况而定,也可以不实现
init(_ key: String, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}
// 必须实现的属性
// 用这个代替被标记属性的set/get方法
var wrappedValue: T {
get {
return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
}
使用的时候
class ViewController1: UIViewController {
@UserDefaultsWrapper(key: "isCheckRule1", defaultValue: false)
var isCheckRule: Bool
// 因为是泛型,所以还可以是Array,Int,String等等
@UserDefaultsWrapper(key: "historySearchKeys", defaultValue: [])
var historySearchKeys: [String]
// ......
// ......
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !isDidCheckRule {
// do something
// ......
// ......
isDidCheckRule = true
}
// ......
// ......
for key in historySearchKeys {
// do something
}
}
}
// ......
// ......
class ViewController2: UIViewController {
@UserDefaultsWrapper(key: "isCheckRule2", defaultValue: false)
var isDidCheckRule: Bool
// ......
// ......
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !isDidCheckRule {
// do something
// ......
// ......
isDidCheckRule = true
}
}
}
上面被标记的属性
class ViewController1: UIViewController {
@UserDefaultsWrapper(key: "isCheckRule1", defaultValue: false)
var isCheckRule: Bool
}
等价于
class ViewController1: UIViewController {
var isDidCheckRule: Bool {
get {
return UserDefaults.standard.object(forKey: "isDidCheckRule1") as? Bool ?? false
}
set {
UserDefaults.standard.set(newValue, forKey: "isDidCheckRule1")
}
}
}
还可以这样使用,比如需要去除前后空格
@propertyWrapper
struct FixString {
private var string: String?
// 只能作为计算属性,不能作为存储属性
var wrappedValue: String? {
get {
return string
}
set {
// 去除前后空格
string = newValue?.trimmingCharacters(in: .whitespaces)
}
}
}
使用
class ViewController1: UIViewController {
@FixString
var text1: String
@FixString
var text2: String
@FixString
var text3: String
}
还可以限制一个值的范围
@propertyWrapper
struct RangeWrapper<Number: Numeric> where Number: Comparable {
private var min: Number
private var max: Number
private var number: Number
var wrappedValue: Number {
get {
return number
}
set {
if newValue > max {
number = max
} else if newValue < min {
number = min
} else {
number = newValue
}
}
}
init(min: Number, max: Number, number: Number) {
self.min = min
self.max = max
self.number = number
}
}
使用
@RangeWrapper(min: 10, max: 100)
var number: Int
补充一点点
- 属性包装器可以用于类、结构体、枚举的属性,不能用于局部和全局变量
- 可以不用实现构造方法根据业务而定
- 拥有属性包装器的属性可以包含 willSet 和 didSet 闭包,但是不能重写编译器合成的 get 和 set 闭包
- wrappedValue 只能作为计算属性,不能作为存储属性
总结一下
可以看出@propertyWrapper其实就是用一个Struct帮我们去除一些重复的set/get代码,而且可以像普通的Struct一样加入泛型,协议约束等,使用方法也非常简单,只要注意wrappedValue是必须实现的wrappedValue是必须实现的wrappedValue是必须实现的
最后
以上全是我个人不成熟的理解,有什么不对之处还希望有大佬能指正
完结撒花~