Swift5 lazy 懶加載詳解

懶加載

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)兒好

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蜻展,隨后出現(xiàn)的幾起案子喉誊,更是在濱河造成了極大的恐慌,老刑警劉巖纵顾,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伍茄,死亡現(xiàn)場離奇詭異,居然都是意外死亡施逾,警方通過查閱死者的電腦和手機(jī)幻林,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門贞盯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人沪饺,你說我怎么就攤上這事躏敢。” “怎么了整葡?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵件余,是天一觀的道長。 經(jīng)常有香客問我遭居,道長啼器,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任俱萍,我火速辦了婚禮端壳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘枪蘑。我一直安慰自己损谦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布岳颇。 她就那樣靜靜地躺著照捡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪话侧。 梳的紋絲不亂的頭發(fā)上栗精,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機(jī)與錄音瞻鹏,去河邊找鬼悲立。 笑死,一個胖子當(dāng)著我的面吹牛新博,可吹牛的內(nèi)容都是我干的薪夕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼叭披,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了玩讳?” 一聲冷哼從身側(cè)響起涩蜘,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎熏纯,沒想到半個月后同诫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡樟澜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年误窖,在試婚紗的時候發(fā)現(xiàn)自己被綠了叮盘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡霹俺,死狀恐怖柔吼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情丙唧,我是刑警寧澤愈魏,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站想际,受9級特大地震影響培漏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜胡本,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一牌柄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧侧甫,春花似錦珊佣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至僻爽,卻和暖如春虫碉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背胸梆。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工敦捧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人碰镜。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓兢卵,卻偏偏與公主長得像猿棉,于是被迫代替她去往敵國和親皱埠。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評論 2 353