一、示例代碼
import UIKit let obj = TestObj() class TestObj { init() { print("\(type(of: self)) init") } } class ViewController: UIViewController { func test() { let x = obj } override func viewDidLoad() { super.viewDidLoad() print("ViewController start view didload") DispatchQueue.main.asyncAfter(deadline: .now() + 5) { for _ in 0...100000 { DispatchQueue.global().async { let x = obj print(x) } } } } }
問題,TestObj 中的初始化方法何時調用?
二、經過測試
方法在 viewDidLoad 的 log “ViewController start view didload”輸出5s之後纔會調用
因爲swift中static修飾的全局變量或者類變量都是lazy load,也就是第一次使用的時候纔會初始化,並且這個初始化過程是原子化的
三、多線程安全分析
結論:
對於只讀的全局變量,在多線程環境下面讀是安全的;
對於可變的全局變量,多線程下面寫是不安全的,可能造成引用計數不正確,錯誤釋放。
具體可參考:
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html
爲什麼只讀的情況下面多線程訪問是安全的,可以看下面的方法
這個方法的反彙編(DEBUG模式下面,編譯器優化之後可能有變化)
int _$s14GlobalVarTest214ViewControllerC4testyyF() { rax = GlobalVarTest2.obj.unsafeMutableAddressor : GlobalVarTest2.TestObj(); rax = *rax; var_18 = rax; swift_retain(rax); rax = swift_release(var_18); return rax; }
可以看到全局變量在這個作用域內是先進行了retain操作,最後進行了release。
對一個對象的retain和release操作,本身這個方法是原子操作的,所以不用擔心這兩個方法的線程安全問題。
如果是全局可寫的變量,當兩個線程對這個對象進行改變的時候
retain和release中間會出現穿插,導致業務上引用計數的變化不再安全。
因此,對於全局變量寫操作,必須進行加鎖,保證retain和release在正確的邏輯中。