swift 屬性包裝器

背景

開發(fā)過(guò)程正中可愛(ài)的產(chǎn)品總會(huì)給我們提一個(gè)這樣的需求


“這個(gè)頁(yè)面用戶第一次進(jìn)入的時(shí)候彈個(gè)框顯示一下我們的用戶協(xié)議,點(diǎn)完確認(rèn)之后不再顯示?!?/p>

收到這樣的需求腦子里第一個(gè)冒出的想法就是“簡(jiǎn)單,用在UserDefaults里放個(gè)key,每次進(jìn)來(lái)的時(shí)候取一下就好了,有就代表顯示過(guò)了,沒(méi)有就代表沒(méi)顯示過(guò)”

        let value = UserDefaults.standard.object(forKey: "key1") as? Bool ?? false
        if !value {
          // do something
          // ......
          // ......
            
            UserDefaults.standard.set(true, forKey: "key1")
            UserDefaults.standard.synchronize()
        }

然后過(guò)些日子產(chǎn)品又來(lái)這個(gè)地方再加個(gè)“使用規(guī)則”


于是上面的代碼換了個(gè)key復(fù)制了

然后再過(guò)些日子...,于是就出現(xiàn)了“key2”,“key3”,“key4”...上面的代碼寫得到處都是。等你回頭來(lái)review的時(shí)候發(fā)現(xiàn)...

@propertyWrapper

這個(gè)時(shí)候就可以用@propertyWrapper屬性包裝器來(lái)解決

@propertyWrapper  // 標(biāo)記這個(gè)struct是個(gè)屬性包裝器
struct UserDefaultsWrapper<T> {
    private let key: String
    private let defaultValue: T
    // 這個(gè)實(shí)例方法根據(jù)業(yè)務(wù)情況而定,也可以不實(shí)現(xiàn)
    init(_ key: String, defaultValue: T) {
        self.key = key
        self.defaultValue = defaultValue
    }
    // 必須實(shí)現(xiàn)的屬性
    // 用這個(gè)代替被標(biāo)記屬性的set/get方法
    var wrappedValue: T {
        get {
            return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
        }
        set {
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }
}

使用的時(shí)候

class ViewController1: UIViewController {
    @UserDefaultsWrapper(key: "isCheckRule1", defaultValue: false)
    var isCheckRule: Bool

     // 因?yàn)槭欠盒停赃€可以是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
        }
    }
}


上面被標(biāo)記的屬性

class ViewController1: UIViewController {
    @UserDefaultsWrapper(key: "isCheckRule1", defaultValue: false)
    var isCheckRule: Bool
}

等價(jià)于

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?
    // 只能作為計(jì)算屬性,不能作為存儲(chǔ)屬性
    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
}

還可以限制一個(gè)值的范圍

@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

補(bǔ)充一點(diǎn)點(diǎn)

  • 屬性包裝器可以用于類、結(jié)構(gòu)體、枚舉的屬性,不能用于局部和全局變量
  • 可以不用實(shí)現(xiàn)構(gòu)造方法根據(jù)業(yè)務(wù)而定
  • 擁有屬性包裝器的屬性可以包含 willSet 和 didSet 閉包,但是不能重寫編譯器合成的 get 和 set 閉包
  • wrappedValue 只能作為計(jì)算屬性,不能作為存儲(chǔ)屬性

總結(jié)一下

可以看出@propertyWrapper其實(shí)就是用一個(gè)Struct幫我們?nèi)コ恍┲貜?fù)的set/get代碼,而且可以像普通的Struct一樣加入泛型,協(xié)議約束等,使用方法也非常簡(jiǎn)單,只要注意wrappedValue是必須實(shí)現(xiàn)的wrappedValue是必須實(shí)現(xiàn)的wrappedValue是必須實(shí)現(xiàn)的

最后

以上全是我個(gè)人不成熟的理解,有什么不對(duì)之處還希望有大佬能指正
完結(jié)撒花~


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容