屬性包裝器,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
這句代碼的調用路徑:函數
number = min(newValue, 12)
來保證新設置的值小於等於 12。將上面的 SmallRectangle 改寫爲下面的代碼你會發現報錯 - Argument passed to call that takes no arguments
:oop
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 來獲取。
@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
}
複製代碼