基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

Swift lazy (懒加载) 的作用是什么?它一定是线程安全的吗?

知识点图片

简单直接的回答是:

  1. 作用:推迟初始化(以优化性能)和 在初始化时访问 self
  2. 线程安全不是。Swift 的 lazy 属性在多线程环境下不保证只初始化一次。

下面是详细的解释:

一、 lazy (懒加载) 的作用

lazy 关键字用于定义一个存储属性,该属性的初始值直到它第一次被访问时才会被计算。

主要有以下两个核心用途:

1. 优化性能(避免不必要的开销)

如果一个属性的初始化非常耗时(例如:读取文件、复杂的计算、网络请求),或者该属性在对象的生命周期中可能根本不会被用到,使用 lazy 可以避免在对象初始化(init)时就进行昂贵的操作。

plaintext
class DataManager {
    // 只有当 importer 被首次调用时,才会创建 Importer 实例
    lazy var importer = Importer() 
    var data = [String]()
}

2. 初始化依赖于 self

普通的属性在初始化时,不能引用 self(因为此时实例还未完全初始化)。但是 lazy 属性的闭包在执行时,实例已经初始化完成,因此可以访问 self。这在配置 UI 组件或设置代理时非常有用。

plaintext
class MyViewController: UIViewController {
    lazy var customButton: UIButton = {
        let btn = UIButton(type: .system)
        // 这里可以使用 self,因为 lazy 闭包执行时 self 已经存在
        btn.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
        return btn
    }()
    
    @objc func buttonTapped() { print("Tapped") }
}

二、 它一定是线程安全的吗?

答案:不是。

Swift 官方文档明确指出:

If a property marked with the lazy modifier is accessed by multiple threads simultaneously and the property hasn’t yet been initialized, there’s no guarantee that the property will be initialized only once.

(如果一个被标记为 lazy 的属性在没有初始化时被多个线程同时访问,无法保证该属性只会被初始化一次。)

为什么不安全?

lazy 只是一个语法糖,它在底层并没有加锁机制。
如果线程 A 访问了该属性,发现它为空,开始计算初始值;在线程 A 计算完成并赋值之前,线程 B 也访问了该属性,发现它还是空的,于是线程 B 也开始计算。
结果:初始化闭包会被执行两次(甚至多次),最终属性的值取决于哪个线程最后完成赋值。这可能导致资源浪费,甚至在某些情况下(如对象不允许多次创建)导致逻辑错误或崩溃。

特例对比:static let (全局/静态常量)

需要区分的是,Swift 中的 static let(全局变量或静态属性)是线程安全的

plaintext
class Singleton {
    // 这是线程安全的,底层使用了 dispatch_once
    static let shared = Singleton() 
}

但是实例级别的 lazy var 不是线程安全的。


三、 如何实现线程安全的懒加载?

如果你需要在多线程环境下安全地使用懒加载,不能直接依赖 lazy 关键字,你需要手动加锁。

方法 1:使用 GCD (dispatch_barrier 或串行队列)

plaintext
class ThreadSafeClass {
    private var _heavyObject: HeavyObject?
    private let queue = DispatchQueue(label: "com.example.lazyQueue")

    var heavyObject: HeavyObject {
        // 同步访问队列
        return queue.sync {
            if let obj = _heavyObject {
                return obj
            }
            let newObj = HeavyObject()
            _heavyObject = newObj
            return newObj
        }
    }
}

方法 2:使用锁 (NSLockos_unfair_lock)

plaintext
class ThreadSafeClass {
    private var _heavyObject: HeavyObject?
    private let lock = NSLock()

    var heavyObject: HeavyObject {
        lock.lock()
        defer { lock.unlock() }
        
        if let obj = _heavyObject {
            return obj
        }
        
        let newObj = HeavyObject()
        _heavyObject = newObj
        return newObj
    }
}

总结

  • lazy:用于延迟加载和在初始化块中使用 self。必须是 var
  • 线程安全lazy 具备线程安全性。如果在多线程环境中使用,必须手动加锁保护。
00:00
00:00