美文网首页专注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捕获外界变量原理

    本文中分析两个问题:1. Block闭包是一个引用类型2. Block捕获外部变量 1、Block结构 1.1 I...

  • block底层原理探究(二):内存管理

    前篇block底层原理探究(一):捕获,我们探究了block捕获外部变量的原理;如果block捕获的是对象类型的a...

  • OC底层原理探索-block(下)

    本篇文章我们来探索下block的底层原理实现,栈区block是如何拷贝的堆区的,block捕获外部变量的本质,bl...

  • Block底层原理

    Block底层原理 block本身也是一个oc对象,他内部有一个isa指针。 block可以捕获局部变量,对于全局...

  • OC中block底层原理总结(下)

    关于OC中block的本质结构、block的变量捕获方式请查看OC中block底层原理总结(上)需要先看懂上篇文章...

  • iOS原理篇(五):Block探究

    Block原理 Block变量捕获 Block类型 copy操作和Block内部访问对象类型的变量 __block...

  • block分析(下)

    block通过clang分析 带着下面的疑问,我们去探索block原理 探索block底层源码 block在底层是...

  • block使用及其底层原理

    一 block基本使用 二 block底层结构 三 block变量捕获 四 block的类型 五 block对象类...

  • iOS底层-- block

    手动目录循环引用block的类Block的相关信息block本质block如何捕获外界变量?__block修饰的本...

  • block:block捕获变量

    一、block捕获变量根儿上的东西 1、block会捕获局部变量 2、block不会捕获全局变量二、block捕获...

网友评论

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

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