懶加載
lazy var 本質(zhì)上是聲明并執(zhí)行的閉包月腋,或一個有返回值的函數(shù)調(diào)用
只執(zhí)行一次嚼酝,使用的時候一定不為空
瞬發(fā)閉包(Immdiately-applied closures)匣缘,它是自動@noescape的。這就意味著在這個閉包中無需加[unowned self]:這里不會產(chǎn)生引用循環(huán)摆碉。
- 分配獨(dú)立的內(nèi)存空間
- 消耗內(nèi)存的
- 值一旦產(chǎn)生就不會再被改變:不會重走懶加載創(chuàng)建代碼弛车,所以懶加載控件不能賦值為nil
用法一:控件懶加載
lazy var btn = { () -> UIButton in
// () -> UILabel in 可以省略前提是已指定類型
// 指定類型不可省略, 因?yàn)橄到y(tǒng)分辨不出閉包返回值的類型
return UIButton(type: .custom)
}()
常見寫法
lazy var btn: UIButton = {
return UIButton(type: .custom)
}()
//或
lazy var btn: UIButton = UIButton(type: .custom)
用法二:避免必選屬性在初始化階段進(jìn)行不必要的操作
例如:如果沒有l(wèi)azy滨砍,因?yàn)槌跏蓟A段必選屬性必須有值往湿,smallImage 必須經(jīng)歷不必要的初始化。
class Avatar {
lazy var smallImage: UIImage = self.image.resizedTo(Avatar.defaultSmallSize)
var image: UIImage
init(image: UIImage) {
self.image = largeImage
}
}
用法三:lazy sequences
適用于SequenceType 和 CollectionType
在高階函數(shù)(map flatMap)之前加上 lazy
實(shí)際上惋戏,這些類型其實(shí)就是保留了一個對“原序列”的引用领追,又保留了一個對“待調(diào)用閉包”的引用,然后只在某個元素被訪問時再對這個元素調(diào)用該閉包响逢,做出實(shí)際的計(jì)算蔓腐。
弊端:計(jì)算出的返回值并沒有被緩存(memoization),再次調(diào)用龄句,再次進(jìn)入閉包走流程
func increment(x: Int) -> Int {
print("Computing next value of \(x)")
return x+1
}
let array = Array(0..<1000)
let incArray = array.map(increment)
print("Result:")
print(incArray[0], incArray[4])
// Computing next value of 0
// ......
// Computing nexr value of 999
// Result:
// 1 5
對這段代碼來說,在我們訪問 incArray 的值之前散罕,所有的輸出值都被計(jì)算出來了分歇。所以在 print("Result:")被執(zhí)行之前你會看到有 1000 行 Computing next value of …!即使我們只讀了[0]和[4]這兩個條目欧漱,根本就沒關(guān)心其他剩下的职抡。
解決辦法:Swift 標(biāo)準(zhǔn)庫中,SequenceType 和 CollectionType 協(xié)議都有個叫 lazy 的計(jì)算屬性误甚,它能給我們返回一個特殊的 LazySequence 或者 LazyCollection缚甩。這些類型只能被用在 map,flatMap窑邦,filter這樣的高階函數(shù)中擅威,而且是以一種惰性的方式。
let array = Array(0..<1000)
let incArray = array.lazy.map(increment)
print("Result:")
print(incArray[0], incArray[4])
// Result:
// Computing next value of 0
// Computing next value of 4
// 1 5
那些值被使用時才調(diào)用 increment 函數(shù)冈钦,而不是調(diào)用 map 的時候郊丛。并且只對那些被訪問到的值使用,而不是對整個數(shù)組里面一千個值都使用!
對那些涉及到龐大的序列(比如這個有 1000 個元素的數(shù)組)厉熟、以及高計(jì)算度閉包的情景來說导盅,使用這個技巧會帶來質(zhì)變。
將惰性序列級聯(lián)
惰性序列可以把高階函數(shù)的調(diào)用拼接起來調(diào)用
比如你可以讓一個惰性序列以這種方式調(diào)用 map(或者 flatMap):
func increment(x: Int) -> Int {
print("Computing next value of \(x)")
return x+1
}
func double(x: Int) -> Int {
print("Computing double value of \(x)")
return 2*x
}
let array = Array(0..<1000)
let doubleArray = array.lazy.map(increment).map(double)
print(doubleArray[3])
print(doubleArray[3])
// Computing next value of 3
// Computing double value of 4
// 8
// Computing next value of 3 實(shí)驗(yàn)得知計(jì)算出的返回值并沒有被緩存
// Computing double value of 4 這是一大弊端
// 8
這樣只有當(dāng) array[3] 被訪問時揍瑟,double(increment(array[3])) 才會被執(zhí)行白翻,被訪問之前不會有這個計(jì)算,數(shù)組的其他元素也不會有這個計(jì)算绢片!
與之相對滤馍,如果使用 array.map(increment).map(double)[3](不帶 lazy)會首先對整個 array 序列的所有元素進(jìn)行計(jì)算,所有結(jié)果都計(jì)算出來之后再提取出第4個元素杉畜。更糟糕的是對數(shù)組的迭代要進(jìn)行兩次纪蜒,每個 map 都會有一次。這對計(jì)算時間(computational time)來說是怎樣的一種浪費(fèi)此叠!
不可 lazy let 的原因
這是由lazy 的具體實(shí)現(xiàn)細(xì)節(jié)決定的:它在沒有值的情況下以某種方式被初始化纯续,然后在被訪問時改變自己的值,這就要求該屬性是可變的灭袁。
隱式 lazy
在 class 中使用 static let 是 Swift 創(chuàng)建單例的最佳實(shí)踐猬错,原因在于 static let 是惰性的、線程安全的茸歧,而且只能被創(chuàng)建一次倦炒。
被聲明在全局作用域下、或者被聲明為一個類型屬性(聲明為static let软瞎、而非聲明為實(shí)例屬性)的常量
是自動具有惰性(lazy)的(還是線程安全的)
// 全局變量逢唤,被以 lazy 形式(和一種線程安全的形式)創(chuàng)建
let foo: Int = {
print("Global constant initialized")
return 42
}()
class Cat {
static let defaultName: String = {
print("Type constant initialized")
return "Felix"
}()
}
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
print("Hello")
print(foo)
print(Cat.defaultName)
print("Bye")
return true
}
}
// Hello
// Global constant initialized
// 42
// Type constant initialized
// Felix
// Bye
證明了 foo 和 Cat.defaultName 這兩個常量只在被訪問時才被創(chuàng)建,而非初始化時創(chuàng)建涤浇。
別把這個和class或結(jié)構(gòu)體里面的實(shí)例屬性的情況搞混了鳖藕。如果你聲明一個 struct Foo ,那 bar 這個實(shí)例屬性會在一個 Foo 實(shí)例被創(chuàng)建的時候就被計(jì)算出來(作為其初始化的一部分)只锭,而不是以惰性的形式著恩。
struct Foo {
let bar = Bar()
}
參考資料:
“懶”點(diǎn)兒好