美文网首页
lazy var 和 let

lazy var 和 let

作者: 流星阁 | 来源:发表于2024-03-03 11:55 被阅读0次

之前碰到过一个比较诡异的问题,后续排查可能跟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
    }
}

相关文章

网友评论

      本文标题:lazy var 和 let

      本文链接:https://www.haomeiwen.com/subject/hfmkzdtx.html