讓屬性變懶的 swift lazy 咒語

swift 的 stored property 有著一定要初始的天性,在東西建立時,一定得確保東西的每一個 stored property 都已完成初始( property 有內容或是設為 optional)。不過有些屬性需要花費較久的時間才能完成初始, 為了加快東西建立的時間,我們可以在屬性施加 lazy 咒語,使其在東西建立時偷懶休息。待東西建立後,需要讀取屬性時再做初始。

接下來我們以可愛的寶寶 Baby 類別為例,假設寶寶想養一隻小狗,於是我們在寶寶類別裡宣告指到小狗物件的屬性 cuteDog。由於生小狗不是兩三天,所以我們將它宣告為 lazy。寶寶出生時還不需要小狗陪伴,待某天寶寶覺得空虛寂寞,想要小狗陪時再生小狗哄他開心。

class Dog {
var name = "布丁狗"
init() {
print("小狗出生了")
}
}

class Baby {
var name = "小彼得"
var age = 1
lazy var cuteDog = Dog()
init() {
print("寶寶\(name)出生了")
}
}

let cuteBaby = Baby()

cuteBaby 建立時還不會設定 lazy 加持的 cuteDog 屬性,因此小狗還沒有來到這個世界上。

等寶寶長大,第一次存取 cuteDog 屬性時,才會生出小狗,因此 小狗布丁狗出生了 將在 寶寶小彼得出生了 之後列印。

let cuteBaby = Baby()
print(cuteBaby.cuteDog.name)

在使用 lazy 時,有以下幾點規則須特別注意:

  • let 宣告的屬性不能加上 lazy。

因為 let 會產生不能改變的常數,所以不可能滿足 lazy 後來才改變屬性內容的需求。

  • computed property 不能加上 lazy。

computed property 在讀取時才計算生成東西,早已實現 lazy 後來再建立東西的目的。

  • lazy 的屬性一定要在宣告時指定初始值。

如此它才知道當屬性第一次被存取時,如何設定屬性的內容。

我們都知道 lazy 不好,做人要勤勞才能 rich,但是如果你的屬性內容必須依靠另一個屬性,那你一定要幫它加上 lazy。為什麼呢? 當然是因為他依靠別人,說明他是一個懶惰的屬性呀。

開玩笑的啦,讓我們從以下計算 bmi 的例子說明吧。bmi 屬性需要用另外兩個屬性 weight 和 height 計算。但這樣子是不允許的,因為此時 Baby 尚未生成,weight 和 height 的內容可能還未設定完成。

class Baby {
var weight = 50.0
var height = 1.55
var bmi = weight / (height * height)
}

像剛剛的程式,property 的內容透過其它 property 計算時,將產生錯誤訊息 Cannot use instance member xxx within property initializer; property initializers run before ‘self’ is available。

要消除紅色錯誤,我們須幫 bmi 加上 lazy,如此它將在初次存取時才計算,而此時寶寶早已生成,weight 和 height 都已完成初始,所以可以順利計算 bmi。

class Baby {
var weight = 50.0
var height = 1.55
lazy var bmi = weight / (height * height)
}
let cuteBaby = Baby()
print(cuteBaby.bmi)

lazy property 搭配 closure

有時 property 的初始值需要經過多行程式的計算,此時我們可使用呼叫 closure 的寫法設定,例如以下的 skills。為了讓程式的效能更好,我們還可在前面加上 lazy,讓 closure 等到程式第一次存取 skills 時才執行。

struct Baby {
var name: String
var age: Int
lazy var skills = {
var skills = [String]()
var zodiacs = ["鼠", "牛", "虎", "兔", "龍", "蛇", "馬", "羊", "猴", "雞", "狗", "豬"]
while skills.count != 3 {
let zodiac = zodiacs.randomElement()!
let number = Int.random(in: 1...100)
skills.append("降\(zodiac)\(number)掌")
}
return skills
}()
}

--

--

彼得潘的 iOS App Neverland
彼得潘的 Swift iOS App 開發問題解答集

彼得潘的iOS App程式設計入門,文組生的iOS App程式設計入門講師,彼得潘的 Swift 程式設計入門,App程式設計入門作者,http://apppeterpan.strikingly.com