专注iOS开发的小渣渣iOS程序猿iOS学习笔记

swift底层探索 09 - Block捕获外界变量原理

2021-07-17  本文已影响0人  Henry________

本文中分析两个问题:
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文件:


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)

输出:


二个外部变量时

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

输出:


如果是这样:

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捕获方式更加简洁,但是对编译器的要求就会更高;
上一篇下一篇

猜你喜欢

热点阅读