美文网首页专注iOS开发的小渣渣iOS程序猿iOS学习笔记
swift底层探索 09 - Block捕获外界变量原理

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

作者: Henry________ | 来源:发表于2021-07-17 12:47 被阅读0次

    本文中分析两个问题:
    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捕获方式更加简洁,但是对编译器的要求就会更高;

    相关文章

      网友评论

        本文标题:swift底层探索 09 - Block捕获外界变量原理

        本文链接:https://www.haomeiwen.com/subject/tsqwpltx.html