之前碰到過(guò)一個(gè)比較詭異的問(wèn)題,后續(xù)排查可能跟UI初始化創(chuàng)建使用lazy var 還是 let 有關(guān)犁功,記錄下:
1、private let: 它能夠?yàn)槟悴蛔兊闹堤峁┮粋€(gè)存儲(chǔ)空間。這意味著一旦你賦值給它硼一,你就不能更改這個(gè)值。而且let在聲明時(shí)必須初始化梦抢。
private let myLabel: UILabel = {
let label = UILabel()
label.textColor = UIColor.black
label.textAlignment = .center
return label
}()
2般贼、private lazy var: 顧名思義读拆,lazy變量是在第一次訪問(wèn)時(shí)才會(huì)計(jì)算其初始值的變量栓袖。如果它永遠(yuǎn)沒有被訪問(wèn),那么它就永遠(yuǎn)不會(huì)被初始化咆爽。這對(duì)于可能不需要的比較昂貴的創(chuàng)建過(guò)程來(lái)說(shuō)很有用霞赫,并且允許你延遲加載腮介。
private lazy var myLabel: UILabel = {
let label = UILabel()
label.textColor = UIColor.black
label.textAlignment = .center
return label
}()
private let 更適用于那些你知道將永遠(yuǎn)不會(huì)更改的值,而且在運(yùn)行時(shí)立即初始化绩脆。而 private lazy var 更適用于可能不會(huì)使用或者有可能在運(yùn)行時(shí)改變的值萤厅,允許延遲初始化。
一靴迫、 lazy var 某些場(chǎng)景下會(huì)存在一些需要注意的問(wèn)題
1惕味、場(chǎng)景
多線程: lazy var在多線程環(huán)境下可能會(huì)有問(wèn)題。當(dāng)多個(gè)線程同時(shí)訪問(wèn)一個(gè)lazy屬性時(shí)玉锌,有可能出現(xiàn)這個(gè)屬性被初始化多次名挥。已經(jīng)在后續(xù)的 Swift 版本加入了線程安全的保護(hù),但是依然需要開發(fā)者注意主守。
層級(jí)依賴: 如果你正在初始化的變量依賴于其他尚未初始化的變量禀倔,那么可能會(huì)引發(fā)運(yùn)行時(shí)錯(cuò)誤。為了避免這種情況参淫,你需要保證當(dāng)lazy屬性被訪問(wèn)時(shí)救湖,它所依賴的所有變量都已經(jīng)初始化完畢。
記憶性: lazy var是一個(gè)記憶屬性涎才。這意味著鞋既,一旦它被初始化力九,即使其依賴的其它屬性值有所改變,它自己的值也不會(huì)再發(fā)生改變邑闺。如果你想要一個(gè)屬性能夠動(dòng)態(tài)響應(yīng)其它屬性的改變跌前,那么你可能需要使用計(jì)算屬性(Computed Property)而非lazy var。
class MyViewController: UIViewController {
var shouldShowButton = false
private lazy var myButton: UIButton = {
let button = UIButton()
button.isHidden = !shouldShowButton // 這里
return button
}()
}
在這個(gè)例子中陡舅,myButton只在初始化時(shí)使用了shouldShowButton的值抵乓。換句話說(shuō),即使你在初始化后改變了shouldShowButton的值靶衍,myButton的isHidden屬性也不會(huì)發(fā)生改變灾炭。
2、lazy 屬性可能會(huì)被多次初始化摊灭。這是因?yàn)槎鄠€(gè)線程可能同時(shí)運(yùn)行到初始化代碼咆贬,然后各自完成初始化。這種情況通常不是我們想要的結(jié)果帚呼,特別是當(dāng)初始化資源消耗非常大掏缎。
class MyClass {
private lazy var expensiveObject: ExpensiveObject = {
print("Initializing ExpensiveObject")
return ExpensiveObject()
}()
func useExpensiveObject() {
_ = expensiveObject
}
}
// 創(chuàng)建MyClass的實(shí)例
let myClass = MyClass()
// 創(chuàng)建并行隊(duì)列
let queue = DispatchQueue(label: "com.example.myQueue", attributes: .concurrent)
// 并發(fā)地從兩個(gè)線程訪問(wèn)myClass的lazy屬性
queue.async {
myClass.useExpensiveObject()
}
queue.async {
myClass.useExpensiveObject()
}
在這個(gè)例子中,ExpensiveObject 可能會(huì)被初始化兩次煤杀,因?yàn)閮蓚€(gè)線程可能同時(shí)運(yùn)行到初始化代碼眷蜈,然后各自完成初始化。
然而在 Swift 5.2 之后沈自,lazy var 具備了線程安全性酌儒,也就是說(shuō)即使發(fā)生并發(fā)訪問(wèn),它也只會(huì)被初始化一次枯途。這已經(jīng)消除了線程安全問(wèn)題忌怎。
但是,如果在訪問(wèn) lazy var 時(shí)需要做額外的同步工作酪夷,或者依賴其他可能會(huì)被并行修改的狀態(tài)榴啸,這時(shí)線程安全性就需額外處理了。
例:
// 假設(shè)你有一個(gè)類MyClass晚岭,它包含一個(gè)lazy var和其他一些可能會(huì)被并發(fā)修改的狀態(tài):
class MyClass {
private var count: Int = 0
private lazy var expensiveObject: ExpensiveObject = {
self.count += 1
print("Initializing ExpensiveObject")
return ExpensiveObject()
}()
func useCount() -> Int {
return count
}
func useExpensiveObject() {
_ = expensiveObject
}
}
如果你在并行環(huán)境中訪問(wèn)這個(gè)expensiveObject和修改count鸥印,它可能會(huì)引起非預(yù)期的效果,因?yàn)榭赡芡瑫r(shí)有其他線程在修改count坦报。
解決這個(gè)問(wèn)題的辦法之一是使用鎖:
class MyClass {
private var count: Int = 0
private let lock = NSLock()
private lazy var expensiveObject: ExpensiveObject = {
lock.lock()
defer { lock.unlock() }
count += 1
print("Initializing ExpensiveObject")
return ExpensiveObject()
}()
func useCount() -> Int {
return count
}
func incrementCount() {
lock.lock()
defer { lock.unlock() }
count += 1
}
func useExpensiveObject() {
_ = expensiveObject
}
}