經過 Property Wrappers 簡化代碼

什麼是 Property Wrappers

屬性包裝器,Swift 的特性之一。用來修飾屬性,它能夠抽取關於屬性重複的邏輯來達到簡化代碼的目的。好比線程安全檢查、將數據存到數據庫等。git

如何使用

經過 @propertyWrapper 來標識。下面經過一個例子來講明它是如何使用的。github

基本使用

假設有一個 SmallRectangle 的結構體,它有 height 和 width 兩個屬性,須要將兩個屬性的值都限制在 12 如下。數據庫

根據上述的需求,先建立一個 TwelveOrLess 限制條件的結構體:api

@propertyWrapper
struct TwelveOrLess {
    private var number: Int
    init() { self.number = 0 }
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 12) }
    }
}
複製代碼

在 SmallRectangle 結構體上使用:安全

struct SmallRectangle {
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
}

var rectangle = SmallRectangle()
print(rectangle.height)
// Prints "0"

rectangle.height = 10
print(rectangle.height)
// Prints "10"

rectangle.height = 24
print(rectangle.height)
// Prints "12"
複製代碼

經過上面的代碼能夠看出,使用 @TwelveOrLess 修飾的屬性能夠自動將值限制在 12 及如下。markdown

那麼,當使用 @TwelveOrLess var height: Int 這句代碼時,實際發生了什麼呢?app

// 這句代碼 @TwelveOrLess var height: Int 至關於下面的代碼(編譯器會自動轉爲下面的代碼):
private var _height = TwelveOrLess()
var height: Int {
    get { return _height.wrappedValue }
    set { _height.wrappedValue = newValue }
}
複製代碼

rectangle.height = 24 這句代碼的調用路徑:函數

  • 調用 SmallRectangle height 的 set 函數。
  • 調用 TwelveOrLess wrappedValue 的 set 函數。
  • 調用 number = min(newValue, 12) 來保證新設置的值小於等於 12。

設置初始值

將上面的 SmallRectangle 改寫爲下面的代碼你會發現報錯 - Argument passed to call that takes no argumentsoop

struct SmallRectangle {
    @TwelveOrLess var height: Int = 1
    @TwelveOrLess var width: Int = 1
}
複製代碼

這是由於咱們的 TwelveOrLess 並無提供有參的初始化函數。在 TwelveOrLess 添加有參的初始化函數便可解決:spa

init(wrappedValue: Int) {
    number = min(12, wrappedValue)
}
複製代碼

假如咱們的條件變量 12 也是能夠動態的設置,能夠再添加一個初始化函數,用來設置條件變量:

init(wrappedValue: Int, maximum: Int) {
    self.maximum = maximum
    number = min(maximum, wrappedValue)
}
複製代碼

使用 init(wrappedValue: Int, maximum: Int) 初始化屬性:

struct NarrowRectangle {
    @TwelveOrLess(wrappedValue: 2, maximum: 5) var height: Int
    @TwelveOrLess(wrappedValue: 3, maximum: 4) var width: Int
}

var narrowRectangle = NarrowRectangle()
print(narrowRectangle.height, narrowRectangle.width)
// Prints "2 3"

narrowRectangle.height = 100
narrowRectangle.width = 100
print(narrowRectangle.height, narrowRectangle.width)
// Prints "5 4"
複製代碼

Tips:當沒有給 @TwelveOrLess 修飾的變量賦初始值時,默認使用 init() 初始化。

struct ZeroRectangle {
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
}

var zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width)
// Prints "0 0"
複製代碼

ProjectedValue

projectedValue 用來獲取你定義邏輯的一些額外狀態值。

好比在上面的例子中,你想獲取你設置的值是否超過了限定的最大值,這個就能夠用 projectedValue 來獲取。

@propertyWrapper
struct TwelveOrLess {
    private var number: Int
    private var maximum: Int
    var projectedValue: Bool
    
    var wrappedValue: Int {
        get { return number }
        set {
            if newValue > 12 {
                number = 12
                projectedValue = true
            } else {
                number = newValue
                projectedValue = false
            }
        }
    }
    
    init() {
        projectedValue = false
        self.number = 0
        self.maximum = 12
    }
    
    init(wrappedValue: Int) {
        projectedValue = false
        maximum = 12
        number = min(maximum, wrappedValue)
    }
    
    init(wrappedValue: Int, maximum: Int) {
        projectedValue = false
        self.maximum = maximum
        number = min(maximum, wrappedValue)
    }
}
複製代碼

獲取狀態值:

struct SomeStructure {
    @TwelveOrLess var someNumber: Int
}
var someStructure = SomeStructure()

someStructure.someNumber = 4
print(someStructure.$someNumber)
// Prints "false"

someStructure.someNumber = 55
print(someStructure.$someNumber)
// Prints "true"
複製代碼

經過 $+屬性名的方式來獲取 projectedValue.

當設值爲 4 的時候,沒有大於 12,沒有觸發條件,因此 $someNumber 爲 false;當設值爲 55 的時候,大於 12,觸發了條件,因此 $someNumber 爲 true。

實際項目應用

  • 將字符串字符所有小寫:
@propertyWrapper
struct LowerLetter {
    private var value = ""
    var wrappedValue: String {
        set {
            value = newValue.lowercased()
        }
        get { return value}
    }
    
    init(wrappedValue: String) {
        value = wrappedValue.lowercased()
    }
}

struct Person {
    @LowerLetter var name: String
}

let p = Person(name: "ABCd")
p.name // abcd
複製代碼
// 聲明
@propertyWrapper
public final class Delegated1<Input> {
    public init() { self.callback = { _ in } }
    private var callback: (Input) -> Void
    public var wrappedValue: (Input) -> Void { return callback }
    public var projectedValue: Delegated1<Input> { return self }
}

public extension Delegated1 {
    func delegate<Target: AnyObject>(
        to target: Target,
        with callback: @escaping (Target, Input) -> Void) {
        self.callback = { [weak target] input in
            guard let target = target else {
                return
            }
            return callback(target, input)
        }
    }
}

// 使用
final class TextField {
    @Delegated1 var didUpdate: (String) -> ()
}

let textField = TextField()

textField.$didUpdate.delegate(to: self) { (self, text) in
    // `self` is weak automatically!
    self.label.text = text
}
複製代碼
相關文章
相關標籤/搜索