Swift類型屬性底層研究

我們研究過成員屬性的一些具體實現(xiàn)細節(jié),本文我們來研究下類型屬性的底層邏輯。

基本語法

  • 類型屬性的語法和成員屬性類似的地方包括:可以定義存儲屬性和計算屬性,也可以添加存儲屬性監(jiān)聽器
struct Sequence {
    static var first: Int = 1 // 存儲屬性
    static var second: Int {  // 計算屬性
        get {
            return first
        }
        set {
            first = newValue
        }
    }
    static var third: Int = 3 { // 存儲添加屬性監(jiān)聽器
        didSet {
            print("third didSet")
        }
        willSet {
            print("third willSet")
        }
    }
}

區(qū)別是類型屬性要用static進行修飾

  • 類型屬性不能用lazy修飾,因為類型屬性默認就是already-lazy global
不能用lazy修飾

swift_once

實現(xiàn)分析

類型屬性默認是懶加載,我們來看看底層的實現(xiàn)邏輯。

<!-- 測試代碼 -->
struct Sequence {
    static var first: Int = 1
}

func test() {
    Sequence.first = 2
}

test()
  • 獲取Sequence.first的內(nèi)存地址:Sequence.first.unsafeMutableAddressor
Sequence.first.unsafeMutableAddressor
  • 如果地址不存在,利用swift_once進行變量的初始化
swift_once
  • swift_once底層調(diào)用的是dispatch_once_f
dispatch_once_f

我們得知:編譯器會封裝一個初始化函數(shù),作為dispatch_once_ffn參數(shù)進行初始化調(diào)用

  • fn函數(shù)封裝
// one-time initialization function for first
sil private [global_init_once_fn] @$s4main8SequenceV5first_WZ : $@convention(c) () -> () {
bb0:
  alloc_global @$s4main8SequenceV5firstSivpZ      // id: %0
  %1 = global_addr @$s4main8SequenceV5firstSivpZ : $*Int // user: %4
  %2 = integer_literal $Builtin.Int64, 1          // user: %3
  %3 = struct $Int (%2 : $Builtin.Int64)          // user: %4
  store %3 to %1 : $*Int                          // id: %4
  %5 = tuple ()                                   // user: %6
  return %5 : $()                                 // id: %6
} 

通過SIL分析,我們得知:編譯器會封裝一個初始化函數(shù),大體的實現(xiàn)邏輯是:

  • 得到變量的內(nèi)存地址, 類似于:var ptr = withUnsafePointer(to: &Sequence.first) { $0 }
  • 將1賦值給這個內(nèi)存地址上,類似于:ptr.pointee = 2
總結
  • 編譯器會將var first: Int = 1封裝成一個函數(shù),函數(shù)體是先獲取變量指針,然后給指針所指的內(nèi)存地址賦值為初始值
  • 類型屬性底層是通過dispatch_once_f進行初始化,確保只會初始化一次,并且是線程安全的

全局變量

從圖一的編譯器錯誤提示我們可以得知,類型屬性本質(zhì)就是全局變量,只是有訪問權限限定。

let zero: Int = 0

struct Sequence {
    static var first: Int = 1
}

我們利用實例代碼進行分析。

SIL分析
@_hasStorage @_hasInitialValue var zero: Int { get set }

struct Sequence {
  @_hasStorage @_hasInitialValue static var first: Int { get set }
  init()
}

// zero
sil_global hidden @$s4main4zeroSivp : $Int

// static Sequence.first
sil_global hidden @$s4main8SequenceV5firstSivpZ : $Int

我們看到SIL語法中,全局變量和類型屬性的定義是完全相同的。

內(nèi)存驗證
func test() {
    let ptr1 = withUnsafePointer(to: &zero) { UnsafeRawPointer($0) }
    let ptr2 = withUnsafePointer(to: &Sequence.first) { UnsafeRawPointer($0) }
    print("\(ptr1) \(ptr2)")
}
// 0x100008000 0x100008008

通過查看內(nèi)存地址,我們得到的結果是zeroSequence.first的內(nèi)存地址是連續(xù)挨在一起的。zero肯定是全局變量,所以Sequence.first本質(zhì)上也是一個全局變量。

全局變量的更多用法

既然類型屬性是全局變量,那全局變量應該也可以是計算屬性等。其實確實也是可以這樣寫的:

var zero: Int = 0
var one: Int {
    get {
        zero
    }
    set {
        zero = newValue
    }
}
var two: Int = 2 {
    willSet {
        
    }
    didSet {
        
    }
}

全局變量的語法和類型屬性的語法也是一致的。

變量內(nèi)存安全(參考地址

前面我們看到了類型屬性本質(zhì)是通過swift_once得到了變量內(nèi)存地址指針。Swift編譯器可以(也僅僅只有編譯器可以)獲取到全局變量的內(nèi)存地址指針。

為什么需要獲取變量的內(nèi)存地址指針呢?這涉及到內(nèi)存安全的部分

Swift會保證同時訪問同一塊內(nèi)存時不會沖突,通過約束代碼里對于存儲地址的寫操作,去獲取那一塊內(nèi)存的訪問獨占權。避免了讀寫沖突。

變量內(nèi)存安全是通過swift_beginAccessswift_endAccess等方法類控制的。

變量內(nèi)存安全
swift_beginAccess
swift_beginAccess
AccessSet::insert

邏輯總結:

  1. 先將內(nèi)存指針封裝成Access對象
  2. Access對象的封裝的內(nèi)存指針如果在SwiftTLSContext::get().accessSet數(shù)組中不存在,說明目前沒有其他方法占用該內(nèi)存地址,可以訪問,并且將Access對象保存起來;
  3. Access對象的封裝的內(nèi)存指針如果在SwiftTLSContext::get().accessSet數(shù)組中存在,說明該內(nèi)存地址已經(jīng)有訪問存在了,如果所有的訪問都是讀訪問,則不認為是沖突,可以繼續(xù)訪問,否則就會報訪問沖突錯誤。
swift_endAccess
swift_endAccess

邏輯總結:
將當前的訪問從SwiftTLSContext::get().accessSet數(shù)組中移除,也就是將本次內(nèi)存訪問移除。

總結

  • 類型屬性本質(zhì)上是全局變量,只是訪問權限有所限制
  • 類型屬性和全局變量可以是存儲屬性,計算屬性,也可以添加屬性監(jiān)聽器,但是不能添加懶加載的lazy關鍵字
  • 類型屬性是懶加載的,通過dispatch_once_f進行, 確保只會初始化一次,并且是線程安全的
  • 編譯器對類型屬性和全局變量添加了內(nèi)存安全的控制,避免了訪問的讀寫沖突,使代碼更加安全
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容