Lazy
lazy關(guān)鍵字的作用是在第一次使用屬性的時(shí)候才去生成撞秋,而不是在一開始就初始化好饰豺,按需使用齐莲。
當(dāng)計(jì)算屬性的值比較耗時(shí)痢站,或者需要外部值的依賴時(shí),用lazy比較合適选酗。
struct Animal {
lazy var name: String = {
// ...
return ...
}()
}
lazy必須配合var使用阵难,因?yàn)閘et需要有個(gè)初始值。
當(dāng)使用lazy屬性時(shí)芒填,struct對(duì)象必須聲明為var呜叫,因?yàn)樵谠L問屬性時(shí)會(huì)改變?cè)搶?duì)象。如果像下面這樣殿衰,會(huì)報(bào)錯(cuò)朱庆。
let animal = Animal()
print(animal.name)
// error: Cannot use mutating getter on immutable value: 'animal' is a 'let' constant.
global和static修飾的屬性都是lazy的。
自己實(shí)現(xiàn)lazy
知道了lazy代表的意思闷祥,我們可以自己實(shí)現(xiàn)lazy娱颊。主要思路就是判斷該值是否計(jì)算過,如果沒有,就計(jì)算箱硕,返回結(jié)果拴竹,如果已經(jīng)計(jì)算過了,就直接返回結(jié)果剧罩。下面將會(huì)用到enum關(guān)聯(lián)值和簡(jiǎn)單的模式匹配栓拜。
定義
首先來定義enum
private enum LazyValue<T> {
case NotComputed(() -> T)
case Computed(T)
}
如果未計(jì)算過,就用func計(jì)算惠昔,并且將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
}
}
}
這里利用了一個(gè)中間值_value
來存儲(chǔ)其狀態(tài),當(dāng)要真正獲取value的時(shí)候舰罚,判斷_value
關(guān)聯(lián)屬性纽门,然后進(jì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ù)只會(huì)執(zhí)行一次营罢,所以assert(box.value == 20)
不成立。
小優(yōu)化
每次取值都是box.value饼齿,顯得有些不太方便饲漾,所以我們?cè)偬峁┮粋€(gè)直接屬性來獲取value。
struct Animal {
private let _name = LazyBox<String> {
return "animal"
}
var name: String {
return _name.value
}
}
let animal = Animal()
animal.name
并發(fā)
如果在多線程情況下缕溉,在計(jì)算value時(shí)是不安全的考传。有可能出現(xiàn)計(jì)算多次的情況。為了解決這個(gè)問題证鸥,可以用同步隊(duì)列僚楞,一次只能一個(gè)線程訪問,保證只會(huì)計(jì)算一次枉层。
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!
}
}
這可能會(huì)有點(diǎn)性能下降泉褐,因?yàn)樵谧x取的時(shí)候同樣也是在同步隊(duì)列中讀取,但是影響不會(huì)很大鸟蜡。
蘋果文檔中也說道膜赃,lazy進(jìn)行計(jì)算時(shí)是線程不安全的。
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.