之前碰到过一个比较诡异的问题,后续排查可能跟UI初始化创建使用lazy var 还是 let 有关,记录下:
1、private let: 它能够为你不变的值提供一个存储空间。这意味着一旦你赋值给它,你就不能更改这个值。而且let在声明时必须初始化。
private let myLabel: UILabel = {
let label = UILabel()
label.textColor = UIColor.black
label.textAlignment = .center
return label
}()
2、private lazy var: 顾名思义,lazy变量是在第一次访问时才会计算其初始值的变量。如果它永远没有被访问,那么它就永远不会被初始化。这对于可能不需要的比较昂贵的创建过程来说很有用,并且允许你延迟加载。
private lazy var myLabel: UILabel = {
let label = UILabel()
label.textColor = UIColor.black
label.textAlignment = .center
return label
}()
private let 更适用于那些你知道将永远不会更改的值,而且在运行时立即初始化。而 private lazy var 更适用于可能不会使用或者有可能在运行时改变的值,允许延迟初始化。
一、 lazy var 某些场景下会存在一些需要注意的问题
1、场景
多线程: lazy var在多线程环境下可能会有问题。当多个线程同时访问一个lazy属性时,有可能出现这个属性被初始化多次。已经在后续的 Swift 版本加入了线程安全的保护,但是依然需要开发者注意。
层级依赖: 如果你正在初始化的变量依赖于其他尚未初始化的变量,那么可能会引发运行时错误。为了避免这种情况,你需要保证当lazy属性被访问时,它所依赖的所有变量都已经初始化完毕。
记忆性: lazy var是一个记忆属性。这意味着,一旦它被初始化,即使其依赖的其它属性值有所改变,它自己的值也不会再发生改变。如果你想要一个属性能够动态响应其它属性的改变,那么你可能需要使用计算属性(Computed Property)而非lazy var。
class MyViewController: UIViewController {
var shouldShowButton = false
private lazy var myButton: UIButton = {
let button = UIButton()
button.isHidden = !shouldShowButton // 这里
return button
}()
}
在这个例子中,myButton只在初始化时使用了shouldShowButton的值。换句话说,即使你在初始化后改变了shouldShowButton的值,myButton的isHidden属性也不会发生改变。
2、lazy 属性可能会被多次初始化。这是因为多个线程可能同时运行到初始化代码,然后各自完成初始化。这种情况通常不是我们想要的结果,特别是当初始化资源消耗非常大。
class MyClass {
private lazy var expensiveObject: ExpensiveObject = {
print("Initializing ExpensiveObject")
return ExpensiveObject()
}()
func useExpensiveObject() {
_ = expensiveObject
}
}
// 创建MyClass的实例
let myClass = MyClass()
// 创建并行队列
let queue = DispatchQueue(label: "com.example.myQueue", attributes: .concurrent)
// 并发地从两个线程访问myClass的lazy属性
queue.async {
myClass.useExpensiveObject()
}
queue.async {
myClass.useExpensiveObject()
}
在这个例子中,ExpensiveObject 可能会被初始化两次,因为两个线程可能同时运行到初始化代码,然后各自完成初始化。
然而在 Swift 5.2 之后,lazy var 具备了线程安全性,也就是说即使发生并发访问,它也只会被初始化一次。这已经消除了线程安全问题。
但是,如果在访问 lazy var 时需要做额外的同步工作,或者依赖其他可能会被并行修改的状态,这时线程安全性就需额外处理了。
例:
// 假设你有一个类MyClass,它包含一个lazy var和其他一些可能会被并发修改的状态:
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
}
}
如果你在并行环境中访问这个expensiveObject和修改count,它可能会引起非预期的效果,因为可能同时有其他线程在修改count。
解决这个问题的办法之一是使用锁:
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
}
}
网友评论