返回

Swift 之通过汇编探究闭包的本质

IOS

导言

闭包是 Swift 中一项强大的特性,它允许开发者创建可以在其他函数和方法中捕获和传递的代码块。与 Objective-C 中的块类似,Swift 闭包提供了简洁优雅的方式来处理异步任务、事件处理和数据转换等各种场景。

汇编视角下的闭包

为了深入理解闭包的本质,我们将使用汇编语言来探索其底层实现。在 Swift 中,闭包被编译为 Objective-C 块,因此我们可以通过查看汇编代码来了解其内部结构。

ARM64 汇编

以下是 ARM64 架构上闭包的汇编代码示例:

        .section __TEXT, __text,regular,pure_instructions
        .align 2
        .globl _main
_main:                                  // function main (inlined)
        stp fp, lr, [sp, -16]!
        add fp, sp, #16
        ldr x0, [fp, #-8]
        bl _Block_byref_0                // call Block_byref
        mov x0, #0
        b _Block_byref_0                // return Block_byref
        .align 2
_Block_byref_0:                            // function Block_byref (inlined)
        stp fp, lr, [sp, -16]!
        add fp, sp, #16
        mov x0, x1
        b _Block_byref_1                // return Block_byref
        .align 2
_Block_byref_1:                            // function Block_byref (inlined)
        stp fp, lr, [sp, -16]!
        add fp, sp, #16
        mov x0, x0                    // result: x0
        ldr x1, [fp, #-24]            // self
        mov x2, x0
        bl _Block_invoke                // call Block_invoke
        mov x0, x0                    // result: x0
        b _Block_byref_2                // return Block_byref
        .align 2
_Block_invoke:                               // function Block_invoke (inlined)
        stp fp, lr, [sp, -16]!
        add fp, sp, #16
        mov x0, x2
        mov x1, x1
        ldr x2, [fp, #-24]            // block
        ldr x2, [x2, #12]             // function pointer
        blx x2                          // call block function
        mov x0, x0                    // result: x0
        b _Block_invoke_2                // return Block_invoke

Objective-C 块结构

从汇编代码中,我们可以看到闭包实际上是 Objective-C 块,具有以下结构:

  • isa 指针: 指向块类(NSBlock)的指针。
  • 函数指针: 指向闭包实现函数的指针。
  • 隐式参数: 指向闭包捕获变量的指针。

闭包捕获

闭包可以捕获其所在作用域中的变量。在汇编代码中,这些捕获变量存储在隐式参数中,并且在闭包调用时传递给闭包实现函数。

闭包调用

当闭包被调用时,汇编代码会将隐式参数传递给闭包实现函数。闭包实现函数使用这些捕获变量来执行闭包内的代码。

编译器优化

LLVM 编译器可以对闭包进行优化,例如:

  • 内联闭包: 如果闭包足够简单,编译器可能会将其内联到调用它的函数中。
  • 消除逃逸闭包: 如果闭包永远不会逃逸其创建的作用域,编译器可能会消除它。

示例

以下 Swift 代码:

let closure: () -> Void = {
  print("Hello, world!")
}

编译为以下汇编代码:

        .section __TEXT, __text,regular,pure_instructions
        .align 2
        .globl _main
_main:                                  // function main (inlined)
        stp fp, lr, [sp, -16]!
        add fp, sp, #16
        ldr x0, [fp, #-8]
        bl _Block_byref_0                // call Block_byref
        mov x0, #0
        b _Block_byref_0                // return Block_byref
        .align 2
_Block_byref_0:                            // function Block_byref (inlined)
        stp fp, lr, [sp, -16]!
        add fp, sp, #16
        mov x0, x1
        b _Block_byref_1                // return Block_byref
        .align 2
_Block_byref_1:                            // function Block_byref (inlined)
        stp fp, lr, [sp, -16]!
        add fp, sp, #16
        mov x0, x0                    // result: x0
        ldr x1, [fp, #-24]            // self
        mov x2, x0
        bl _Block_invoke                // call Block_invoke
        mov x0, x0                    // result: x0
        b _Block_byref_2                // return Block_byref
        .align 2
_Block_invoke:                               // function Block_invoke (inlined)
        stp fp, lr, [sp, -16]!
        add fp, sp, #16
        mov x0, x2
        mov x1, x1
        ldr x2, [fp, #-24]            // block
        ldr x2, [x2, #12]             // function pointer
        blx x2                          // call block function
        mov x0, x0                    // result: x0
        b _Block_invoke_2                // return Block_invoke

结论

通过汇编视角探索闭包,我们加深了对 Swift 中闭包本质和底层实现的理解。我们了解到闭包是编译为 Objective-C 块的代码块,并通过函数指针和隐式参数捕获其所在的变量。此外,我们还了解了编译器如何通过内联和逃逸消除等优化来提高闭包的性能。