swift底層探索 09 - Block捕獲外界變量原理

本文中分析兩個問題:
1. Block閉包是一個引用類型
2. Block捕獲外部變量

1、Block結構

1.1 IR文件分析

獲取IR文件:swiftc -emit-ir 文件地址/main.swift > ./main.ll

func makeIncrementer() -> () -> Int{
    var runningTotal = 10
    //內嵌函數,也是一個閉包
    func incrementer() -> Int{
        runningTotal += 1
        return runningTotal
    }
    return incrementer
}

IR文件:


  • 可以看到使用swift_allocObject來分配堆內存,間接證明Block是引用類型.
  • 但是不是很直觀。

1.2 結構圖

1.3 代碼結構

一個外部變量:

struct FuntionData<T>{
    var ptr: UnsafeRawPointer
    var captureValue: UnsafePointer<Box<T>>
}

struct Box<T> {
    var refCounted: HeapObject
    var value: UnsafePointer<Box<T>>
}

struct HeapObject{
    var type: UnsafeRawPointer
    var refCount: UInt64
}

2、 Block結構仿寫

一個外部變量時

struct FuntionData<T>{
    var ptr: UnsafeRawPointer
    var captureValue: UnsafePointer<Box<T>>
}

struct Box<T> {
    var refCounted: HeapObject
    var value: UnsafePointer<Box<T>>
}

struct HeapObject{
    var type: UnsafeRawPointer
    var refCount: UInt64
}

//閉包的結構體,方便獲取閉包地址
struct VoidIntFun {
    var f: () ->Int
}

func makeIncrementer() -> () -> Int{
    var runningTotal = 10
    //內嵌函數,也是一個閉包
    func incrementer() -> Int{
        runningTotal += 1
        return runningTotal
    }
    return incrementer
}
let makeInc = VoidIntFun(f: makeIncrementer())

let ptr = UnsafeMutablePointer<VoidIntFun>.allocate(capacity: 1)
//初始化的內存空間
ptr.initialize(to: makeInc)
//將ptr重新綁定內存
let ctx = ptr.withMemoryRebound(to: FunctionData<Box<Int>>.self, capacity: 1) { $0.pointee }

print(ctx.ptr)
print(ctx.captureValue.pointee)

輸出:


  • 不論外部變量是是否發生修改,都將包裝成一個Box<T>的結構體

二個外部變量時

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 12
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

輸出:


  • 如果是兩個變量,其中變量二發生了修改(相當于OC中的__block),會包裝成對象并存到捕獲列表;

如果是這樣:

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 12
    func incrementer() -> Int {
        return runningTotal + amount
    }
    return incrementer
}

輸出:


  • 如果沒有發生變化,就直接引用,并不會進行引用類型的包裝;

總結

  1. 引用單個變量時,不論當前變量在Block是否發生了變化,都會被包裝成對象,存在captureValue捕獲列表里
  2. 多個變量時:
    1. 發生變化的外部變量進行對象包裝,然后將指針地址存在捕獲列表里.
    2. 沒有修改的變量就會直接保存變量的值;
  3. 相比之下Swift中的Block捕獲方式更加簡潔,但是對編譯器的要求就會更高;
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。