美文网首页
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