美文网首页
Swift底层原理-闭包

Swift底层原理-闭包

作者: 祀梦_ | 来源:发表于2023-05-17 23:07 被阅读0次

Swift底层原理-闭包

函数类型

  • Swift中函数本身也有自己的类型,它由形式参数类型,返回类型组成。
  • 函数也是一个引用类型
  • 那么函数类型的本质是什么呢,我们打开源码,在Metadata.h文件中找到TargetFunctionTypeMetadata
template <typename Runtime>
struct TargetFunctionTypeMetadata : public TargetMetadata<Runtime> {
  using StoredSize = typename Runtime::StoredSize;
  using Parameter = ConstTargetMetadataPointer<Runtime, swift::TargetMetadata>;

  TargetFunctionTypeFlags<StoredSize> Flags;

  /// The type metadata for the result type.
  ConstTargetMetadataPointer<Runtime, swift::TargetMetadata> ResultType;

  Parameter *getParameters() { return reinterpret_cast<Parameter *>(this + 1); }

  const Parameter *getParameters() const {
    return reinterpret_cast<const Parameter *>(this + 1);
  }

  Parameter getParameter(unsigned index) const {
    assert(index < getNumParameters());
    return getParameters()[index];
  }

  // 省略部分方法
}
  • 由于TargetFunctionTypeMetadata继承自TargetMetadata,那么它必然有Kind

  • 然后它自身又拥有FlagsResultTypeResultType是返回值类型的元数据。

  • 还有一个连续的空间存储的是参数列表

  • 接下来我们看到getParameters函数

Parameter *getParameters() { return reinterpret_cast<Parameter *>(this + 1); }
  • 这个函数通过reinterpret_cast将 (this + 1) 强制转换成Parameter *类型,然后返回的是指针类型。
  • 所以这个函数返回的是一块连续的内存空间,这一块连续的内存空间存储的是Parameter类型的数据。

闭包介绍

  • 闭包是一个可以捕获上下文的常量或者变量的函数

  • 我们先看一下官方给的例子

func makeIncrementer() -> () -> Int {
    var runningTotal = 10
    func incrementer() -> Int {
        runningTotal += 1
        return runningTotal
        
    }
    return incrementer
}
  • 这里incrementer作为一个闭包,显然他是一个函数,其次为了保证其执行,要捕获外部变量runningTotal 到内部,所以闭包的关键就有捕获外部变量或常量函数

  • 闭包的表现形式:

    • 全局函数是一个有名字但不会捕获任何值的闭包。
    • 嵌套函数是一个有名字并可以捕获到其封闭函数域内的值的闭包。
    • 闭包表达式是一个利用轻量级语法所写的,可以捕获其上下文中变量或常量值的匿名闭包。

闭包表达式

  • 闭包表达式是一种利用简洁语法构建内联闭包的方式。闭包表达式提供了一些语法优化,使得撰写闭包变得简单明了。

定义闭包表达式

在使用闭包的时候,可以用下面的方式来定义一个闭包表达式

{ (param type) -> (return type) in
    //do somethings
}
复制代码

可以看到闭包表达式是由作用域(花括号)函数类型关键字in函数体构成

闭包作为变量和参数

  • 作为变量
var closure: (Int) -> Int = { (a: Int) -> Int in
    return a + 100
}
  • 作为参数
func func3(_ someThing: @escaping (() -> Void)) {
    
}

闭包表达式的优点

  • 可以根据上下文推断出参数类型和返回值类型

  • 单行表达式闭包可以通过省略return关键字来隐式返回单行表达式的结果

  • Swift自动为内联闭包提供了参数名称缩写功能,你可以直接通过$0,$1,$2来顺序调用闭包的参数,以此类推。

  • 如果你在闭包表达式中使用参数名称缩写,你可以在闭包定义中省略参数列表,并且对应参数名称缩写的类型会通过函数类型进行推断。in关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成

闭包捕获值

闭包捕获局部变量

  • 我们先来看一个例子
func makeIncrementer() -> () -> Int {
    var runningTotal = 10
    func incrementer() -> Int {
        runningTotal += 1
        return runningTotal
        
    }
    return incrementer
}

let fn = makeIncrementer()
print(fn())
print(fn())
print(fn())

// 打印结果:
// 11
// 12
// 13
  • 每次调用fn,但是每次打印都是不一样的,按理说runningTotal是一个局部变量,每次打印应该结果一致。我们通过sil查看一下
// makeIncrementer()
sil hidden [ossa] @$s4main15makeIncrementerSiycyF : $@convention(thin) () -> @owned @callee_guaranteed () -> Int {
bb0:
  %0 = alloc_box ${ var Int }, var, name "runningTotal" // users: %11, %8, %1
  %1 = project_box %0 : ${ var Int }, 0           // users: %9, %6
  %2 = integer_literal $Builtin.IntLiteral, 10    // user: %5
  %3 = metatype $@thin Int.Type                   // user: %5
  // function_ref Int.init(_builtinIntegerLiteral:)
  %4 = function_ref @$sSi22_builtinIntegerLiteralSiBI_tcfC : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int // user: %5
  %5 = apply %4(%2, %3) : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int // user: %6
  store %5 to [trivial] %1 : $*Int                // id: %6
  // function_ref incrementer #1 () in makeIncrementer()
  %7 = function_ref @$s4main15makeIncrementerSiycyF11incrementerL_SiyF : $@convention(thin) (@guaranteed { var Int }) -> Int // user: %10
  %8 = copy_value %0 : ${ var Int }               // user: %10
  mark_function_escape %1 : $*Int                 // id: %9
  %10 = partial_apply [callee_guaranteed] %7(%8) : $@convention(thin) (@guaranteed { var Int }) -> Int // user: %12
  destroy_value %0 : ${ var Int }                 // id: %11
  return %10 : $@callee_guaranteed () -> Int      // id: %12
} // end sil function '$s4main15makeIncrementerSiycyF'
  1. %0行,通过alloc_box申请了一个堆上的地址,并将地址给了RunningTotal,将变量存储到堆上
  2. %1行,通过project_box从堆上取出变量
  3. %7行,将取出的变量交给闭包使用。
  • 通过汇编验证一下,在makeIncrementer方法内部调用了swift_allocObject方法

[图片上传失败...(image-e82ef2-1684422389259)]

  • swift_allocObject方法,这个方法在干什么,在申请并分配堆空间的内存,所以实际上闭包会开辟堆空间的内存。

闭包捕获全局变量

  • 在捕获局部变量时,会开辟堆内存空间,那么捕获全局变量说呢
var runningTotal = 10
func makeIncrementer() -> () -> Int {
    func incrementer() -> Int {
        runningTotal += 1
        return runningTotal
    }
    return incrementer
}

let fn = makeIncrementer()
print(fn())
print(fn())
print(fn())
  • 我们通过汇编发现,在makeIncrementer中没有进行任何堆内存开辟操作,它直接把函数地址返回出去

[图片上传失败...(image-4e5a32-1684422389259)]

  • 接着进入incrementer方法中

[图片上传失败...(image-f8dac2-1684422389259)]

  • 它是直接拿到全局变量runningTotal直接修改的。所以函数不会去捕获全局变量/常量,因此这种行为严格上也不叫做闭包。

闭包的本质

  • 在我们进行闭包本质探索时,需要借助IR的代码进行分析,我们先来熟悉一下IR部分语法

IR部分语法

  • 数组:
[<elementnumber> x <elementtype>]
//example
alloca [24 x i8], align 8 24个i8都是0
alloca [4 x i32] === array
  • 结构体:
%swift.refcounted = type { %swift.type*, i64 }

//表示形式
%T = type {<type list>} //这种和C语言的结构体类似
  • 指针类型:
<type> *

//example
i64* //64位的整形
  • getelementptr 指令:

LLVM中我们获取数组和结构体的成员,通过 getelementptr ,语法规则如下:

<result> = getelementptr <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}*
<result> = getelementptr inbounds <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx}
  • 这里举个例子
struct munger_struct{
    int f1;
    int f2;
};

// munger_struct 的地址
// i64 0 取出的是 struct.munger_struct类型的指针
getelementptr inbounds %struct.munger_struct, %struct.munger_struct %1, i64 0

// munger_struct 第一个元素
// i64 0 取出的是 struct.munger_struct类型的指针
// i32 0取出的是 struct.munger_struct结构体中的第一个元素
getelementptr inbounds %struct.munger_struct, %struct.munger_struct %1, i64 0, i32 0

// munger_struct 第二个元素
// i64 0 取出的是 struct.munger_struct类型的指针
// i32 1取出的是 struct.munger_struct结构体中的第二个元素
getelementptr inbounds %struct.munger_struct, %struct.munger_struct %1, i64 0, i32 1

分析闭包

  • 把下面这个例子,转换成IR文件
func makeIncrementer() -> () -> Int {
    var runningTotal = 10
    func incrementer() -> Int {
        runningTotal += 1
        return runningTotal
    }
    return incrementer
}

let fn = makeIncrementer()

main函数分析

  • 我们先找到main函数
define i32 @main(i32 %0, i8** %1) #0 {
entry:
  %2 = bitcast i8** %1 to i8*
  // 调用makeIncrementer函数
  %3 = call swiftcc { i8*, %swift.refcounted* } @"$s4main15makeIncrementerSiycyF"()
  %4 = extractvalue { i8*, %swift.refcounted* } %3, 0
  %5 = extractvalue { i8*, %swift.refcounted* } %3, 1
  store i8* %4, i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main2fnSiycvp", i32 0, i32 0), align 8
  store %swift.refcounted* %5, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main2fnSiycvp", i32 0, i32 1), align 8
  ret i32 0
}
  • %3这一行,调用makeIncrementer函数,并且它的返回值是一个 { i8*, %swift.refcounted* },我们全局搜索一下这个结构
%swift.function = type { i8*, %swift.refcounted* }
%swift.refcounted = type { %swift.type*, i64 }
%swift.type = type { i64 }
  • 根据IR语法进行分析:
    • { i8*, %swift.refcounted* }是一个结构体,这个结构体包含两个成员变量,分别为 i8* 类型的成员和 %swift.refcounted* 类型的成员。
    • %swift.refcounted*是一个结构体指针,它的结构为{ %swift.type*, i64 },这个结构体包含两个成员变量,分别为 %swift.type*类型的成员和i64类型的成员。
    • %swift.type是一个结构体,它的结构为 { i64 },它只包含i64类型的成员变量。

makeIncrementer函数分析

  • 接下来进入该函数
define hidden swiftcc { i8*, %swift.refcounted* } @"$s4main15makeIncrementerSiycyF"() #0 {
entry:
  %runningTotal.debug = alloca %TSi*, align 8
  %0 = bitcast %TSi** %runningTotal.debug to i8*
  call void @llvm.memset.p0i8.i64(i8* align 8 %0, i8 0, i64 8, i1 false)
      
  // 调用swift_allocObject创建一个实例对象
  %1 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata, i32 0, i32 2), i64 24, i64 7) #1
  %2 = bitcast %swift.refcounted* %1 to <{ %swift.refcounted, [8 x i8] }>*
  
  // 获取{ %swift.refcounted, [8 x i8] }中第二个元素[8 x i8]
  %3 = getelementptr inbounds <{ %swift.refcounted, [8 x i8] }>, <{ %swift.refcounted, [8 x i8] }>* %2, i32 0, i32 1
  %4 = bitcast [8 x i8]* %3 to %TSi*
  store %TSi* %4, %TSi** %runningTotal.debug, align 8
      
  // 取出局部变量
  %._value = getelementptr inbounds %TSi, %TSi* %4, i32 0, i32 0
  store i64 10, i64* %._value, align 8
  %5 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %1) #1
  call void @swift_release(%swift.refcounted* %1) #1

  // 插入局部变量地址
  %6 = insertvalue { i8*, %swift.refcounted* } { i8* bitcast (i64 (%swift.refcounted*)* @"$s4main15makeIncrementerSiycyF11incrementerL_SiyFTA" to i8*), %swift.refcounted* undef }, %swift.refcounted* %1, 1
  ret { i8*, %swift.refcounted* } %6
}
  • %1行,调用swift_allocObject,创建一个实例,它返回的是一个HeapObject *的结构体指针。

  • %2行,把实力对象强转成{ %swift.refcounted, [8 x i8] },所以%swift.refcounted*指向实例对象

  • %3行,取出{ %swift.refcounted, [8 x i8] }中第二个元素[8 x i8],然后把外部捕获的局部变量存在着里面。

  • %6行,通过insertvalue函数,先把incrementer函数地址赋值给第一个参数,然后将前面创建的堆空间地址赋值给第二个变量

闭包结构还原

  • 通过上面分析,闭包的本质就是 { i8*, %swift.refcounted* } 这样的结构体,i8* 存储的是函数的地址,%swift.refcounted*存储的是一个 { %swift.refcounted, [8 x i8] }结构体。

  • { %swift.refcounted, [8 x i8] }这个结构里,%swift.refcounted执行一个HeapObject * 对象,然后[8 x i8] 存储我们捕获的值

  • 所以最终闭包结构如下

struct ClosureData<Box> {
    /// 函数地址
    var ptr: UnsafeRawPointer
    /// 存储捕获堆空间地址的值
    var object: UnsafePointer<Box>
}

struct Box<T> {
    var heapObject: HeapObject
    // 捕获变量/常量的值
    var value: T
}

struct HeapObject {
    var matedata: UnsafeRawPointer
    var refcount: Int
}

闭包捕获引用类型

  • 把下面例子转换成IR代码
define hidden swiftcc { i8*, %swift.refcounted* } @"$s4main15makeIncrementerSiycyF"() #0 {
entry:
  %test.debug = alloca %T4main4TestC*, align 8
  %0 = bitcast %T4main4TestC** %test.debug to i8*
  call void @llvm.memset.p0i8.i64(i8* align 8 %0, i8 0, i64 8, i1 false)
  %1 = call swiftcc %swift.metadata_response @"$s4main4TestCMa"(i64 0) #7
  %2 = extractvalue %swift.metadata_response %1, 0
  %3 = call swiftcc %T4main4TestC* @"$s4main4TestCACycfC"(%swift.type* swiftself %2)
  store %T4main4TestC* %3, %T4main4TestC** %test.debug, align 8
  %4 = bitcast %T4main4TestC* %3 to %swift.refcounted*
  // 对实例对象引用计数+1
  %5 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %4) #3
  
  // 将实例对象转换成 %swift.refcounted*类型,并存储到%6中
  %6 = bitcast %T4main4TestC* %3 to %swift.refcounted*
  call void bitcast (void (%swift.refcounted*)* @swift_release to void (%T4main4TestC*)*)(%T4main4TestC* %3) #3
  %7 = insertvalue { i8*, %swift.refcounted* } { i8* bitcast (i64 (%swift.refcounted*)* @"$s4main15makeIncrementerSiycyF11incrementerL_SiyFTA" to i8*), %swift.refcounted* undef }, %swift.refcounted* %6, 1
  ret { i8*, %swift.refcounted* } %7
}
  • 在捕获引用类型时候,其实也不需要捕获实例对象,因为它已经在堆区了,就不需要再去创建一个堆空间的实例包裹它了
  • 只需要将它的地址存储到闭包的结构中,操作实例对象的引用计数+1

闭包捕获多个值

  • 将下面例子转换成IR代码
func makeIncrementer() -> () -> Int {
    var runningTotal = 10
    var runningTotal1 = 11
    func incrementer() -> Int {
        runningTotal += 1
        runningTotal1 += runningTotal
        return runningTotal1
    }
    return incrementer
}

let fn = makeIncrementer()
print(fn())
print(fn())
print(fn())

// 打印结果
// 22
// 34
// 47
  • IRmakeIncrementer代码如下
define hidden swiftcc { i8*, %swift.refcounted* } @"$s4main15makeIncrementerSiycyF"() #0 {
entry:
  %runningTotal.debug = alloca %TSi*, align 8
  %0 = bitcast %TSi** %runningTotal.debug to i8*
  call void @llvm.memset.p0i8.i64(i8* align 8 %0, i8 0, i64 8, i1 false)
  %runningTotal1.debug = alloca %TSi*, align 8
  %1 = bitcast %TSi** %runningTotal1.debug to i8*
  call void @llvm.memset.p0i8.i64(i8* align 8 %1, i8 0, i64 8, i1 false)

  // 第一次调用swift_allocObject
  %2 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata, i32 0, i32 2), i64 24, i64 7) #2
  %3 = bitcast %swift.refcounted* %2 to <{ %swift.refcounted, [8 x i8] }>*
  %4 = getelementptr inbounds <{ %swift.refcounted, [8 x i8] }>, <{ %swift.refcounted, [8 x i8] }>* %3, i32 0, i32 1
  %5 = bitcast [8 x i8]* %4 to %TSi*
  store %TSi* %5, %TSi** %runningTotal.debug, align 8
  %._value = getelementptr inbounds %TSi, %TSi* %5, i32 0, i32 0
  store i64 10, i64* %._value, align 8
      
  // 第二次调用swift_allocObject
  %6 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata, i32 0, i32 2), i64 24, i64 7) #2
  %7 = bitcast %swift.refcounted* %6 to <{ %swift.refcounted, [8 x i8] }>*
  %8 = getelementptr inbounds <{ %swift.refcounted, [8 x i8] }>, <{ %swift.refcounted, [8 x i8] }>* %7, i32 0, i32 1
  %9 = bitcast [8 x i8]* %8 to %TSi*
  store %TSi* %9, %TSi** %runningTotal1.debug, align 8
  %._value1 = getelementptr inbounds %TSi, %TSi* %9, i32 0, i32 0
  store i64 11, i64* %._value1, align 8
  %10 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %2) #2
  %11 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %6) #2
      
  // 第三次调用swift_allocObject
  %12 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata.4, i32 0, i32 2), i64 32, i64 7) #2
  %13 = bitcast %swift.refcounted* %12 to <{ %swift.refcounted, %swift.refcounted*, %swift.refcounted* }>*
  // 将第一个变量堆空间,存储在%13中第二个元素位置
  %14 = getelementptr inbounds <{ %swift.refcounted, %swift.refcounted*, %swift.refcounted* }>, <{ %swift.refcounted, %swift.refcounted*, %swift.refcounted* }>* %13, i32 0, i32 1
  store %swift.refcounted* %2, %swift.refcounted** %14, align 8
  // 将第二个变量堆空间,存储在%13中第三个元素位置
  %15 = getelementptr inbounds <{ %swift.refcounted, %swift.refcounted*, %swift.refcounted* }>, <{ %swift.refcounted, %swift.refcounted*, %swift.refcounted* }>* %13, i32 0, i32 2
  store %swift.refcounted* %6, %swift.refcounted** %15, align 8
  call void @swift_release(%swift.refcounted* %6) #2
  call void @swift_release(%swift.refcounted* %2) #2
  %16 = insertvalue { i8*, %swift.refcounted* } { i8* bitcast (i64 (%swift.refcounted*)* @"$s4main15makeIncrementerSiycyF11incrementerL_SiyFTA" to i8*), %swift.refcounted* undef }, %swift.refcounted* %12, 1
  ret { i8*, %swift.refcounted* } %16
}
  • 可以看到,调用了多次swift_allocObject,第一次和第二次调用为了分别存储runningTotalrunningTotal1
  • 第三次swift_allocObject返回的实例对象,被强转至{ %swift.refcounted, %swift.refcounted*, %swift.refcounted* }类型
  • 然后在此之后分别调用了两次getelementptr方法,把前两次创建的实例对象的地址,存在该结构中的第二个元素、第三个元素位置
  • 然后在返回时,把函数地址,和第三次分配的实例对象一起返回出去
  • 根据上面分析,最终闭包结果如下:
struct ClosureData<MutiValue> {
    /// 函数地址
    var ptr: UnsafeRawPointer
    /// 存储捕获堆空间地址的值
    var object: UnsafePointer<MutiValue>
}

struct MutiValue<T1,T2> {
    var object: HeapObject
    var value: UnsafePointer<Box<T1>>
    var value1: UnsafePointer<Box<T2>>
}

struct Box<T> {
    var object: HeapObject
    var value: T
}

struct HeapObject {
    var matedata: UnsafeRawPointer
    var refcount: Int
}
  • 根据以上的分析,捕获单个值和多个值的区别就在于:

    • 单个值中,ClosureData内存储的堆空间地址直接就是这个值所在的堆空间。
    • 而对于捕获多个值,ClosureData内存储的堆空间地址会变成一个可以存储很多个捕获值的结构。
  • 简单来说,从原来直接指向单个实例对象,变成指向一片连续内存空间,内存空间中存储着指向变量的地址

多种不同类型闭包

尾随闭包

  • 如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,你不用写出它的参数标签:
func test(closure: () -> Void) {

}

// 以下是使用尾随闭包进行函数调用
test {
    
}

// 以下是不使用尾随闭包进行函数调用
test(closure: {
    
})

逃逸闭包

  • 当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注@escaping,用来指明这个闭包是允许“逃逸”出这个函数的。

  • 逃逸闭包存在的可能情况:

    • 当闭包被当作属性存储,导致函数完成时闭包生命周期被延长。

    • 当闭包异步执行,导致函数完成时闭包生命周期被延长。

    • 可选类型的闭包默认是逃逸闭包。

  • 逃逸闭包所需的条件:

    • 作为函数的参数传递。

    • 当前闭包在函数内部异步执行或者被存储。

    • 函数结束,闭包被调用,闭包的生命周期未结束。

  • 逃逸闭包 vs 非逃逸闭包 区别

  • 非逃逸闭包:一个接受闭包作为参数的函数,闭包是在这个函数结束前内被调用,即可以理解为闭包是在函数作用域结束前被调用

    • 1、不会产生循环引用,因为闭包的作用域在函数作用域内,在函数执行完成后,就会释放闭包捕获的所有对象
    • 2、针对非逃逸闭包,编译器会做优化:省略内存管理调用
    • 3、非逃逸闭包捕获的上下文保存在栈上,而不是堆上
  • 逃逸闭包:一个接受闭包作为参数的函数,逃逸闭包可能会在函数返回之后才被调用,即闭包逃离了函数的作用域

    • 1、可能会产生循环引用,因为逃逸闭包中需要显式的引用self(猜测其原因是为了提醒开发者,这里可能会出现循环引用了),而self可能是持有闭包变量的(与OCblock的的循环引用类似)
    • 2、一般用于异步函数的返回,例如网络请求
  • 使用建议:如果没有特别需要,开发中使用非逃逸闭包是有利于内存优化的,所以苹果把闭包区分为两种,特殊情况时再使用逃逸闭包

自动闭包

  • 自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。

总结

  • 一个闭包能够从上下文中捕获已经定义的常量/变量,即使其作用域不存在了,闭包仍然能够在其函数体内引用、修改
    • 1、每次修改捕获值:本质修改的是堆区中的value值
    • 2、每次重新执行当前函数,会重新创建新的内存空间
  • 捕获值原理:本质是在堆区开辟内存空间,并将捕获值存储到这个存空间
  • 闭包是一个引用类型(本质是函数地址传递),底层结构为:闭包 = 函数地址 + 捕获变量的地址
  • 函数也是引用类型(本质是结构体,其中保存了函数的地址)

相关文章

  • 成长(8/2000)——面试题合集5

    闭包的底层原理 闭包的底层原理其实就是作用域链被保存下来的原因,具体往下看 作用域链 之前我们讲作用域和预编译的时...

  • Swift底层原理探索5----闭包

    闭包表达式(Closure Expression) 在Swift中,可以通过func定义一个函数,也可以通过闭包表...

  • Swift-闭包

    Swift 闭包 函数 ()->() Swift 中的闭包和 Objective-C 中的 block 类似,闭包...

  • Swift闭包和函数

    函数在Swift中只是一种特殊的闭包,闭包在Swift语言中是一等公民,支持闭包嵌套和闭包传递。Swift中的闭包...

  • Swift底层探索:闭包

    闭包是可以在你的代码中被传递和引用的功能性独立代码块。闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址...

  • Swift 闭包底层探究

    闭包 闭包:一个函数和它所捕获的变量\常量环境组合起来,称为闭包(一般它捕获的是外层函数的局部变量\常量) 可以把...

  • swift 闭包学习记录

    在OC开发中,我们经常用block,block原理我估计应该和swift的闭包差不多,现在一起看看swift中的闭...

  • 闭包(底层捕获原理)

    class Clourse { func outFunc() -> () ->Int{ var tot...

  • swift4 闭包

    swift 闭包 闭包:swift 中 函数是闭包的一种类似于oc的闭包闭包表达式(匿名函数) -- 能够捕获上下...

  • Swift中的闭包

    在Swift中有两种闭包,逃逸闭包(@escaping)和非逃逸闭包(@nonescaping)。从Swift 3...

网友评论

      本文标题:Swift底层原理-闭包

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