Lazy
lazy關(guān)鍵字的作用是在第一次使用屬性的時候才去生成,而不是在一開始就初始化好,按需使用。
當計算屬性的值比較耗時,或者需要外部值的依賴時,用lazy比較合適。
struct Animal {
lazy var name: String = {
// ...
return ...
}()
}
lazy必須配合var使用,因為let需要有個初始值。
當使用lazy屬性時,struct對象必須聲明為var,因為在訪問屬性時會改變該對象。如果像下面這樣,會報錯。
let animal = Animal()
print(animal.name)
// error: Cannot use mutating getter on immutable value: 'animal' is a 'let' constant.
global和static修飾的屬性都是lazy的。
自己實現(xiàn)lazy
知道了lazy代表的意思,我們可以自己實現(xiàn)lazy。主要思路就是判斷該值是否計算過,如果沒有,就計算,返回結(jié)果,如果已經(jīng)計算過了,就直接返回結(jié)果。下面將會用到enum關(guān)聯(lián)值和簡單的模式匹配。
定義
首先來定義enum
private enum LazyValue<T> {
case NotComputed(() -> T)
case Computed(T)
}
如果未計算過,就用func計算,并且將result關(guān)聯(lián)到Computed(T)中。
class LazyBox<T> {
init(computation: () -> T) {
_value = .NotComputed(computation)
}
private var _value: LazyValue<T>
var value: T {
switch self._value {
case .NotComputed(let computation):
let result = computation()
self._value = .Computed(result)
return result
case .Computed(let result):
return result
}
}
}
這里利用了一個中間值_value來存儲其狀態(tài),當要真正獲取value的時候,判斷_value關(guān)聯(lián)屬性,然后進行處理。
Test
var counter = 0
let box = LazyBox<Int> {
counter++
return counter * 10
}
assert(box.value == 10)
//assertion failed
assert(box.value == 20)
assert(counter == 1)
LazyBox所關(guān)聯(lián)的函數(shù)只會執(zhí)行一次,所以assert(box.value == 20)
不成立。
小優(yōu)化
每次取值都是box.value,顯得有些不太方便,所以我們再提供一個直接屬性來獲取value。
struct Animal {
private let _name = LazyBox<String> {
return "animal"
}
var name: String {
return _name.value
}
}
let animal = Animal()
animal.name
并發(fā)
如果在多線程情況下,在計算value時是不安全的。有可能出現(xiàn)計算多次的情況。為了解決這個問題,可以用同步隊列,一次只能一個線程訪問,保證只會計算一次。
class LazyBox<T> {
private var _value: LazyValue<T>
private let queue = dispatch_queue_create("LazyBox", DISPATCH_QUEUE_SERIAL)
init(computation: () -> T) {
_value = .NotComputed(computation)
}
var value: T {
var returnValue: T? = nil
dispatch_sync(queue) {
switch self._value {
case .NotComputed(let computation):
let result = computation()
self._value = .Computed(result)
returnValue = result
case .Computed(let result):
returnValue = result
}
}
return returnValue!
}
}
這可能會有點性能下降,因為在讀取的時候同樣也是在同步隊列中讀取,但是影響不會很大。
蘋果文檔中也說道,lazy進行計算時是線程不安全的。
Note: If a property marked with the lazy modifier is accessed by multiple threads simultaneously and the property has not yet been initialized, there is no guarantee that the property will be initialized only once.