Swift探索(七): 闭包

一:函数类型

每个函数都有种特定的函数类型,函数的类型由函数的参数类型和返回类型组成。

func addOne(_ a: Int, _ b: Int) -> Int {
    return a + b
}

func addTwo(_ a: Double, _ b: Double) -> Double {
    return a + b
}

var a: (Double, Double) -> Double = addTwo

上述代码中 (Double, Double) -> Double 就是函数类型
函数在 Swift 中是引用类型,也有自己的 metadata

/// The structure of function type metadata.
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];
  }

  ParameterFlags getParameterFlags(unsigned index) const {
    assert(index < getNumParameters());
    auto flags = hasParameterFlags() ? getParameterFlags()[index] : 0;
    return ParameterFlags::fromIntValue(flags);
  }

  StoredSize getNumParameters() const {
    return Flags.getNumParameters();
  }
  FunctionMetadataConvention getConvention() const {
    return Flags.getConvention();
  }
  bool isAsync() const { return Flags.isAsync(); }
  bool isThrowing() const { return Flags.isThrowing(); }
  bool isSendable() const { return Flags.isSendable(); }
  bool isDifferentiable() const { return Flags.isDifferentiable(); }
  bool hasParameterFlags() const { return Flags.hasParameterFlags(); }
  bool isEscaping() const { return Flags.isEscaping(); }
  bool hasGlobalActor() const { return Flags.hasGlobalActor(); }

  static constexpr StoredSize OffsetToFlags = sizeof(TargetMetadata<Runtime>);

  static bool classof(const TargetMetadata<Runtime> *metadata) {
    return metadata->getKind() == MetadataKind::Function;
  }

  uint32_t *getParameterFlags() {
    return reinterpret_cast<uint32_t *>(getParameters() + getNumParameters());
  }

  const uint32_t *getParameterFlags() const {
    return reinterpret_cast<const uint32_t *>(getParameters() +
                                              getNumParameters());
  }

  TargetFunctionMetadataDifferentiabilityKind<StoredSize> *
  getDifferentiabilityKindAddress() {
    assert(isDifferentiable());
    void *previousEndAddr = hasParameterFlags()
        ? reinterpret_cast<void *>(getParameterFlags() + getNumParameters())
        : reinterpret_cast<void *>(getParameters() + getNumParameters());
    return reinterpret_cast<
        TargetFunctionMetadataDifferentiabilityKind<StoredSize> *>(
        llvm::alignAddr(previousEndAddr,
                        llvm::Align(alignof(typename Runtime::StoredPointer))));
  }

  TargetFunctionMetadataDifferentiabilityKind<StoredSize>
  getDifferentiabilityKind() const {
    if (isDifferentiable()) {
      return *const_cast<TargetFunctionTypeMetadata<Runtime> *>(this)
          ->getDifferentiabilityKindAddress();
    }
    return TargetFunctionMetadataDifferentiabilityKind<StoredSize>
        ::NonDifferentiable;
  }

  ConstTargetMetadataPointer<Runtime, swift::TargetMetadata> *
  getGlobalActorAddr() {
    assert(hasGlobalActor());
    
    void *endAddr =
        isDifferentiable()
          ? reinterpret_cast<void *>(getDifferentiabilityKindAddress() + 1) :
        hasParameterFlags()
          ? reinterpret_cast<void *>(getParameterFlags() + getNumParameters()) :
        reinterpret_cast<void *>(getParameters() + getNumParameters());
    return reinterpret_cast<
        ConstTargetMetadataPointer<Runtime, swift::TargetMetadata> *>(
          llvm::alignAddr(
              endAddr, llvm::Align(alignof(typename Runtime::StoredPointer))));
  }

  ConstTargetMetadataPointer<Runtime, swift::TargetMetadata>
  getGlobalActor() const {
    if (!hasGlobalActor())
      return ConstTargetMetadataPointer<Runtime, swift::TargetMetadata>();

    return *const_cast<TargetFunctionTypeMetadata<Runtime> *>(this)
      ->getGlobalActorAddr();
  }
};

Swift源码 中的 Metadata.h 文件中找到 TargetFunctionTypeMetadata 的声明,可以发现 TargetFunctionTypeMetadata 是继承自 TargetMetadata ,通过前一篇文章 Mirror源码解析中我们已经分析过 TargetMetadata 他里面有一个 kind 属性。这里可以看见有个TargetFunctionTypeFlags 类型的 Flags 属性和 ResultType 属性也就是返回值类型, 还有一个连续的内存数组空间 Parameter(存储参数类型),进入到 TargetFunctionTypeFlags

class TargetFunctionTypeFlags {
  // If we were ever to run out of space for function flags (8 bits)
  // one of the flag bits could be used to identify that the rest of
  // the flags is going to be stored somewhere else in the metadata.
  enum : int_type {
    NumParametersMask      = 0x0000FFFFU,
    ConventionMask         = 0x00FF0000U,
    ConventionShift        = 16U,
    ThrowsMask             = 0x01000000U,
    ParamFlagsMask         = 0x02000000U,
    EscapingMask           = 0x04000000U,
    DifferentiableMask     = 0x08000000U,
    GlobalActorMask        = 0x10000000U,
    AsyncMask              = 0x20000000U,
    SendableMask           = 0x40000000U,
    // NOTE: The next bit will need to introduce a separate flags word.
  };
  int_type Data;
  ...
};

可以看到这里标识了很多类型的函数,可以通过这些字段来判断函数是什么类型。
因此可以定义出函数类型结构体如下

struct TargetFunctionTypeMetadata{
    var kind: Int
    var flags: Int
    var arguments: ArgumentsBuffer<Any.Type>
    
    // 获取参数数量
    func numberArguments() -> Int{
        return self.flags & 0x0000FFFF
    }
}

struct ArgumentsBuffer<Element>{
    var element: Element

    mutating func buffer(n: Int) -> UnsafeBufferPointer<Element> {
        return withUnsafePointer(to: &self) {
            let ptr = $0.withMemoryRebound(to: Element.self, capacity: 1) { start in
                return start
            }
            return UnsafeBufferPointer(start: ptr, count: n)
        }
    }

    mutating func index(of i: Int) -> UnsafeMutablePointer<Element> {
        return withUnsafePointer(to: &self) {
            return UnsafeMutablePointer(mutating: UnsafeRawPointer($0).assumingMemoryBound(to: Element.self).advanced(by: i))
        }
    }
}

其中 argumentsFieldDescriptor 中的 fields 属性一样。

// 获取addTwo函数的类型
let value = type(of: addTwo)
let functionType = unsafeBitCast(value as Any.Type, to: UnsafeMutablePointer<TargetFunctionTypeMetadata>.self)
print(functionType.pointee.numberArguments())

打印结果
2

二:什么是闭包

2.1. 闭包的定义

闭包是一个捕获了上下文的常量或者变量的函数。

func makeIncrementer() -> () -> Int {
    var runningTotal = 10
    func incrementer() -> Int {
        runningTotal += 1
        return runningTotal
    }
    return incrementer
}

var a = makeIncrementer()

根据官方文档提供的代码,我们可以看到这里定义了一个外部函数 makeIncrementer 返回类型是 () -> Int 是一个没有参数并且返回值为 Int 的函数类型,在函数中定义了一个内部函数 incrementer() 返回值为 Int ,在内部函数 incrementer() 中使用了外部函数 makeIncrementer() 的变量 runningTotal , 外部函数 makeIncrementer() 最后返回这个内部函数 incrementer() 。var a = makeIncrementer() 即为将 incrementer() 函数赋值给变量 a,这里外部函数 makeIncrementer() 即访问完成,而内部函数 incrementer()a 调用的时候才会进行访问,因此内部函数 incrementer() 的生命周期要比外部函数 makeIncrementer() 长,对于 runningTotal 来说在外部函数 makeIncrementer() 调用完就释放了,但是内部函数还在使用 incrementer() 因此内部函数 incrementer()runningTotal 捕获到内部函数内部。综上所述将 runningTotalincrementer() 称之为闭包。

2.2. 闭包表达式

{ (param) -> (returnType) in
    //do something
}
  • 作用域(也就是大括号)
  • 参数和返回值
  • 函数体( in )之后的代码

2.3. 闭包的书写

  • 闭包作为变量
var closure: (Int) -> Int = { (age: Int) in
    return age
}
  • 闭包声明成一个可选类型
var closure : ((Int) -> Int)?
closure = nil
  • 闭包声明成一个常量
let closure: (Int) -> Int
closure = {(age: Int) in
    return age
}
  • 闭包作为函数的参数
func test(param : () -> Int){
    print(param())
}
var age = 10
test { () -> Int in
    age += 1
    return age
}

三:尾随闭包

当我们把闭包表达式作为函数的最后一个参数,如果当前的闭包表达式很?,我们可以通过尾随闭包的书写方式来提高代码的可读性。

func test(_ a: Int, _ b: Int, by:(_ item1: Int, _ item2: Int) -> Bool) -> Bool {
    return by(a, b)
}

// 不使用尾随闭包
test(10, 20, by: {(_ item1: Int, _ item2: Int) -> Bool in
    return item1 > item2
})

// 使用尾随闭包
test(10, 20) { item1, item2 in
    return item1 > item2
}

其中闭包表达式是 Swift 语法。使用闭包表达式能更简洁的传达信息。当然闭包表达式的好处有很多:

  • 利用上下文推断参数和返回值类型
  • 单表达式可以隐士返回,既省略 return 关键字
  • 参数名称的简写(比如我们的 $0)
  • 尾随闭包表达式
var array = [1, 2, 3]

array.sort(by: {(item1: Int, item2: Int) -> Bool in return item1 < item2})

array.sort(by: {(item1, item2) -> Bool in return item1 < item2})

array.sort{(item1, item2) in item1 < item2 }

array.sort{ return $0 < $1 }

array.sort{ $0 < $1 }

array.sort(by: <)

四:捕获值

4.1 Block捕获值

- (void)testBlock {
    NSInteger i = 1;
    void(^block)(void) = ^{
        NSLog(@"block %ld", i);
    };
    i += 1;
    NSLog(@"before block %ld", i);
    block();
    NSLog(@"after block %ld", i);
}

var t = Test.init()
t.testBlock()

打印结果
before block 2
block 1
after block 2

如果想要外部的修改能够影响当前 block 内部捕获的值,我们只需要对当前的 i 添加 __block 修饰符

- (void)testBlock {
    __block NSInteger i = 1;
    void(^block)(void) = ^{
        NSLog(@"block %ld", i);
    };
    i += 1;
    NSLog(@"before block %ld", i);
    block();
    NSLog(@"after block %ld", i);
}

var t = Test.init()
t.testBlock()

打印结果
before block 2
block 2
after block 2

4.2 闭包捕获值

4.2.1 闭包捕获全局变量

block 换成 swift 的闭包

var i = 1
let closure = {
    print("closure \(i)")
}
i += 1
print("before closure \(i)")
closure()
print("after closure \(i)")

打印结果
before closure 2
closure 2
after closure 2

发现和 OCblock 不一样,通过命令 swiftc main.swift -emit-sil 编译成 SIL 代码定位到 closure() 的定义

// closure #1 in 
sil private [ossa] @$s4mainyycfU_ : $@convention(thin) () -> () {
bb0:
  %0 = global_addr @$s4main1iSivp : $*Int         // user: %32
  %1 = integer_literal $Builtin.Word, 1           // user: %3
  // function_ref _allocateUninitializedArray<A>(_:)
  %2 = function_ref 
...
} // end sil function '$s4mainyycfU_'

可以看到 这里获取的是 s4main1iSivp 的全局地址,而 s4main1iSivp 就是定义的全局变量 i

// i
sil_global hidden @$s4main1iSivp : $Int

这里是直接获取的 i 的地址中的值,因此闭包是不会捕获全局变量的。

4.2.2 闭包捕获局部变量

将这些代码放在函数当中调用

func test() {
    var i = 1
    let closure = {
        print("closure \(i)")
    }
    i += 1
    print("before closure \(i)")
    closure()
    print("after closure \(i)")
}

test()

打印结果
before closure 2
closure 2
after closure 2

编译成 SIL 代码

// closure #1 in test()
sil private [ossa] @$s4main4testyyFyycfU_ : $@convention(thin) (@guaranteed { var Int }) -> () {
// %0 "i"                                         // user: %1
bb0(%0 : @guaranteed ${ var Int }):
  %1 = project_box %0 : ${ var Int }, 0           // users: %34, %2
  debug_value_addr %1 : $*Int, var, name "i", argno 1 // id: %2
  %3 = integer_literal $Builtin.Word, 1           // user: %5
  ...
} // end sil function '$s4main4testyyFyycfU_'

可以看到这里没有再调用 i 的地址,而是通过project_box 从堆上取出变量地址,因此闭包是会捕获局部变量的。

五:闭包的本质

5.1:IR语法

通过上面的分析我们知道函数在 Swift 中是引用类型,并且有自己的 metadata,那么闭包的本质是什么呢?通过对 SIL 文件我们看不出什么,因此我们再降一级在 IR 文件中分析。
对于 IR 的语法网上有很多教程,这里我举几个常用的例子

  • 数组
[<elementnumber> x <elementtype>]
alloca [24 x i8], align 8   24个i8都是0
alloca [4 x i32] === array

elementnumber 当前元素的个数
elementtype 当前元素的类型

  • 结构体
%swift.refcounted = type { %swift.type*, i64 } // %swift.type* 的指针类型,i64 64位的整型
//表示形式
%T = type {<type list>} //这种和C语言的结构体类似

{ 开始 } 结束 type list 元素列表

  • 指针类型
<type> *
// example
i64* //64位的整形
  • bitcase 指令
%2 = bitcast i8** %1 to i8*

指针类型的转换

  • getelementptr 指令
    LLVM 中我们获取数组和结构体的成员,通过 getelementptr ,语法规则如下
<result> = getelementptr <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}*
<result> = getelementptr inbounds <ty>, <ty>* <ptrval>{, [inrange] <ty> <id
// 官方案例
struct munger_struct {
    int f1;
    int f2;
};

void munge(struct munger_struct *P) {
    P[0].f1 = P[1].f1 + P[2].f2;
};

创建一个 Test.c 文件并将上述代码编写其中,通过 clang 指令 clang -S -fobjc-arc -emit-llvm Test.cTest.c 文件编译成 IR 文件

// 结构体定义
%struct.munger_struct = type { i32, i32 }

; Function Attrs: noinline nounwind optnone ssp uwtable
define void @munge(%struct.munger_struct* %0) #0 {
  // 存储结构体首地址
  %2 = alloca %struct.munger_struct*, align 8
  // %struct.munger_struct** 二级指针
  store %struct.munger_struct* %0, %struct.munger_struct** %2, align 8
  %3 = load %struct.munger_struct*, %struct.munger_struct** %2, align 8
  // %struct.munger_struct 当前索引基本类型 %struct.munger_struct* 当前索引结构体地址 i64 1 当前数组Index
  %4 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %3, i64 1
  // 第一个 i32 0 结构体指针偏移0字节 相当于不偏移 第二个 i32 0 获取第一个结构体成员
  %5 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %4, i32 0, i32 0
  %6 = load i32, i32* %5, align 4
  %7 = load %struct.munger_struct*, %struct.munger_struct** %2, align 8
  %8 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %7, i64 2
  %9 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %8, i32 0, i32 1
  %10 = load i32, i32* %9, align 4
  %11 = add nsw i32 %6, %10
  %12 = load %struct.munger_struct*, %struct.munger_struct** %2, align 8
  %13 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %12, i64 0
  %14 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %13, i32 0, i32 0
  store i32 %11, i32* %14, align 4
  ret void
}

getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %3, i64 1
%struct.munger_struct 当前索引基本类型
%struct.munger_struct* 当前索引结构体地址
i64 1 当前数组 Index

%5 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %4, i32 0, i32 0
第一个 i32 0 结构体指针偏移 0 字节 相当于不偏移
第二个 i32 0 获取第一个结构体成员

总结

  • 第?个索引不会改变返回指针的类型,也就是说 ptrval 前?的 * 对应什么类型,就返回什么类型
  • 第?个索引的偏移量是由第?个索引的值和第?个 ty 指定的基本类型共同确定的
  • 后?的索引是在数组或结构体内进?索引
  • 每增加?个索引,就会使该索引使?的基本类型和返回指针的类型去掉?层

5.2:通过IR分析闭包

了解 IR 语法后我们将以下代码通过 swiftc main.swift -emit-ir > ./main.ll 生成 .ll 文件

func makeIncrementer() -> () -> Int {
    var runningTotal = 10
    func incrementer() -> Int {
        runningTotal += 1
        return runningTotal
    }
    return incrementer
}

let makeInc = makeIncrementer()

IR 代码

%swift.function = type { i8*, %swift.refcounted* }
%swift.refcounted = type { %swift.type*, i64 }
%swift.type = type { i64 }
%swift.full_boxmetadata = type { void (%swift.refcounted*)*, i8**, %swift.type, i32, i8* }
%TSi = type <{ i64 }>

···

define i32 @main(i32 %0, i8** %1) #0 {
entry:
  %2 = bitcast i8** %1 to i8*
  %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* @"$s4main7makeIncSiycvp", i32 0, i32 0), align 8
  store %swift.refcounted* %5, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main7makeIncSiycvp", i32 0, i32 1), align 8
  ret i32 0
}

main 函数中

  • %2 = bitcast i8** %1 to i8* 指针类型的转换
  • %3 = call swiftcc { i8*, %swift.refcounted* } @"$s4main15makeIncrementerSiycyF"() 调用了 s4main15makeIncrementerSiycyF 函数。通过 LLVM 指令 xcrun swift-demangle s4main15makeIncrementerSiycyF 可以得到 $s4main15makeIncrementerSiycyF ---> main.makeIncrementer() -> () -> Swift.Int 也就意味着这里调用的是 makeIncrementer() 函数 并且返回的是 { i8*, %swift.refcounted* } 结构体类型,有两个元素一个 i8* 8位整型的指针,一个 %swift.refcounted* 类型的指针 。通过上面的 %swift.refcounted = type { %swift.type*, i64 }%swift.type = type { i64 } 可以得出 %swift.refcounted 就是一个 { i64* , i64*} 的结构体,其实这里的 %swift.type = type { i64 } 就是 metaData 由此可以还原出
struct ClosureData {
    var unkown: UnsafeRawPointer
    var refcount: RefCount
}

struct RefCount {
    var metaData: UnsafeRawPointer
    var refcount1: Int32
    var refcount2: Int32
}
  • %4 = extractvalue { i8*, %swift.refcounted* } %3, 0 就是取 i8* 的值
  • %5 = extractvalue { i8*, %swift.refcounted* } %3, 1 就是取 %swift.refcounted* 的值,也就是 %5 就是 RefCount 结构体的首地址
  • store i8* %4, i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main7makeIncSiycvp", i32 0, i32 0), align 8 这里的 %swift.function 就是 %swift.function = type { i8*, %swift.refcounted* } ,通过 LLVM 指令 xcrun swift-demangle s4main7makeIncSiycvp 可以得到 $s4main7makeIncSiycvp ---> main.makeInc : () -> Swift.Int 。那么这句代码的意思就是将 %4 存储到 makeInc 这个变量 { i8*, %swift.refcounted* } 结构体中第一个元素 i8*
  • store %swift.refcounted* %5, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main7makeIncSiycvp", i32 0, i32 1), align 8 那么这句代码的意思就是将 %5 就是 RefCount 结构体的首地址存储到 makeInc 这个变量 { i8*, %swift.refcounted* } 结构体中第二个元素 %swift.refcounted*

那么这里具体存储的是什么呢?定位到 $s4main15makeIncrementerSiycyF

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)
  %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] }>*
  %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
}
  • { i8*, %swift.refcounted* } @"$s4main15makeIncrementerSiycyF"() 可以看到 $s4main15makeIncrementerSiycyF 这个就是一个包含了一个 i8* 的值和 swift.refcounted 的指针的结构体。
  • %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 这里的 %1 就是堆空间的内存地址
  • %3 = getelementptr inbounds <{ %swift.refcounted, [8 x i8] }>, <{ %swift.refcounted, [8 x i8] }>* %2, i32 0, i32 1 取出 8 x i8
  • %4 = bitcast [8 x i8]* %3 to %TSi*8 x i8 转换成 TSi 类型,其中 TSi%TSi = type <{ i64 }> 是一个 64 位的结构体
  • %._value = getelementptr inbounds %TSi, %TSi* %4, i32 0, i32 0 获取 TSi 结构体 (也就是 struct Int { i64 }) 的 value 元素
  • store i64 10, i64* %._value, align 810 存储上面取的 value 里面
  • %6 = insertvalue { i8*, %swift.refcounted* } { i8* bitcast (i64 (%swift.refcounted*)* @"$s4main15makeIncrementerSiycyF11incrementerL_SiyFTA" to i8*), %swift.refcounted* undef }, %swift.refcounted* %1, 1 其中 insertvalue 是插入的意思,$s4main15makeIncrementerSiycyF11incrementerL_SiyFTA 通过 LLVM 指令 xcrun swift-demangle s4main15makeIncrementerSiycyF11incrementerL_SiyFTA 可以得到 $s4main15makeIncrementerSiycyF11incrementerL_SiyFTA ---> partial apply forwarder for incrementer #1 () -> Swift.Int in main.makeIncrementer() -> () -> Swift.Int , 也就意味着这里将内嵌函数 incrementer() 转换成 i8* ( void * ) 插入到结构体 { i8*, %swift.refcounted* } 中的一个元素 i8 * 也就是将内嵌函数 incrementer() 的函数地址存放到结构体中的一个元素。第二个元素 %swift.refcounted* %1, 1 就是将 %1 也就是开辟的实例对象的内存空间,并且上面已经将值 10 存入到了内存空间当中,所以第二个插入的元素就是一个实例对象内存地址。

5.3:闭包的结构还原

由上述分析可以还原出

struct ClosureData {
    var ptr: UnsafeRawPointer
    var object: HeapObject
}

struct HeapObject {
    var metaData: UnsafeRawPointer
    var refcount1: Int32
    var refcount2: Int32
}

SIL 文件中可以看到他是用 Box 进行了一层包裹,那么由此可以还原出

struct ClosureData<Box>{
    var ptr: UnsafeRawPointer
    var object: UnsafePointer<Box>
}

struct HeapObject {
    var matedata: UnsafeRawPointer
    var refcount1: Int32
    var refcount2: Int32
}

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

因此闭包的本质就是: 闭包的执行地址 + 捕获变量堆空间的地址

接下来验证一下

func makeIncrementer() -> () -> Int {
    var runningTotal = 10
    func incrementer() -> Int {
        runningTotal += 1
        return runningTotal
    }
    return incrementer
}

struct ClosureData<Box>{
    var ptr: UnsafeRawPointer
    var object: UnsafePointer<Box>
}

struct HeapObject {
    var matedata: UnsafeRawPointer
    var refcount1: Int32
    var refcount2: Int32
}

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

// 结构体是值类型 所以demoStruct 就是 f
struct demoStruct {
    var f:() -> Int
}
var f = demoStruct(f: makeIncrementer())

let ptr = UnsafeMutablePointer<demoStruct>.allocate(capacity: 1)
ptr.initialize(to: f)

// 内存属性绑定
let ctx = ptr.withMemoryRebound(to: ClosureData<Box<Int>>.self, capacity: 1) {
    $0.pointee
}

print(ctx.ptr)
print(ctx.object)
print("end")

打印结果:
0x0000000100002b10
0x0000000101022d70
end

接着我们在 mach-o 文件中查找一下这个地址 0x0000000100002b10 是什么 通过 LLVM 命令 nm -p mach-o文件地址 | grep 0000000100002b10 开看一下

0x0000000100002b10在mach-o中是什么.png

这里可以看到 0x0000000100002b10mach-o 中是 s19LJLSwiftClosureDemo15makeIncrementerSiycyF11incrementerL_SiyFTA 这玩意儿,有了上面的经验我们转换一下还是通过 LLVM 命令 xcrun swift-demangle s19LJLSwiftClosureDemo15makeIncrementerSiycyF11incrementerL_SiyFTA 可以得到
s19LJLSwiftClosureDemo15makeIncrementerSiycyF11incrementerL_SiyFTA.png

因此可以得到结论 0x0000000100002b10 就是在函数 makeIncrementer() 中的内嵌函数 incrementer()
print("end")处打个断点打印一下 0x0000000101022d70
0x0000000100002b10.png

5.4:闭包捕获多个值的结构

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

var a = makeIncrementer(amount: 15)

转换成 IR 代码

define i32 @main(i32 %0, i8** %1) #0 {
entry:
  %2 = bitcast i8** %1 to i8*
  %3 = call swiftcc { i8*, %swift.refcounted* } @"$s4main15makeIncrementer6amountSiycSi_tF"(i64 15)
  %4 = extractvalue { i8*, %swift.refcounted* } %3, 0
  %5 = extractvalue { i8*, %swift.refcounted* } %3, 1
  store i8* %4, i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1aSiycvp", i32 0, i32 0), align 8
  store %swift.refcounted* %5, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1aSiycvp", i32 0, i32 1), align 8
  ret i32 0
}

不难得出 $s4main15makeIncrementer6amountSiycSi_tF 就是 makeIncrementer() 函数,定位到 $s4main15makeIncrementer6amountSiycSi_tF

define hidden swiftcc { i8*, %swift.refcounted* } @"$s4main15makeIncrementer6amountSiycSi_tF"(i64 %0) #0 {
entry:
  %amount.debug = alloca i64, align 8
  %1 = bitcast i64* %amount.debug to i8*
  call void @llvm.memset.p0i8.i64(i8* align 8 %1, i8 0, i64 8, i1 false)
  %runningTotal.debug = alloca %TSi*, align 8
  %2 = bitcast %TSi** %runningTotal.debug to i8*
  call void @llvm.memset.p0i8.i64(i8* align 8 %2, i8 0, i64 8, i1 false)
  store i64 %0, i64* %amount.debug, align 8

  %3 = 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
  %4 = bitcast %swift.refcounted* %3 to <{ %swift.refcounted, [8 x i8] }>*
  %5 = getelementptr inbounds <{ %swift.refcounted, [8 x i8] }>, <{ %swift.refcounted, [8 x i8] }>* %4, i32 0, i32 1
  %6 = bitcast [8 x i8]* %5 to %TSi*
  store %TSi* %6, %TSi** %runningTotal.debug, align 8
  %._value = getelementptr inbounds %TSi, %TSi* %6, i32 0, i32 0
  store i64 10, i64* %._value, align 8
  %7 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %3) #2

  %8 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata.3, i32 0, i32 2), i64 32, i64 7) #2
  %9 = bitcast %swift.refcounted* %8 to <{ %swift.refcounted, %swift.refcounted*, %TSi }>*
  %10 = getelementptr inbounds <{ %swift.refcounted, %swift.refcounted*, %TSi }>, <{ %swift.refcounted, %swift.refcounted*, %TSi }>* %9, i32 0, i32 1
  store %swift.refcounted* %3, %swift.refcounted** %10, align 8
  %11 = getelementptr inbounds <{ %swift.refcounted, %swift.refcounted*, %TSi }>, <{ %swift.refcounted, %swift.refcounted*, %TSi }>* %9, i32 0, i32 2
  %._value1 = getelementptr inbounds %TSi, %TSi* %11, i32 0, i32 0
  store i64 %0, i64* %._value1, align 8
  call void @swift_release(%swift.refcounted* %3) #2

  %12 = insertvalue { i8*, %swift.refcounted* } { i8* bitcast (i64 (%swift.refcounted*)* @"$s4main15makeIncrementer6amountSiycSi_tF11incrementerL_SiyFTA" to i8*), %swift.refcounted* undef }, %swift.refcounted* %8, 1
  ret { i8*, %swift.refcounted* } %12
}

我们可以看到有两次 @swift_allocObject 的调用,第一次的 @swift_allocObject 与上面分析单个捕获值的闭包时的 IR 代码差不多, 第二次 @ swift_allocObject 可以看到这里有个结构体是 { %swift.refcounted, %swift.refcounted*, %TSi } 并且

  • %10 = getelementptr inbounds <{ %swift.refcounted, %swift.refcounted*, %TSi }>, <{ %swift.refcounted, %swift.refcounted*, %TSi }>* %9, i32 0, i32 1 取出 { %swift.refcounted, %swift.refcounted*, %TSi } 结构体的第二个元素 %swift.refcounted*
  • store %swift.refcounted* %3, %swift.refcounted** %10, align 8%3 存储在 %10 中, %3 就是第一个 @ swift_allocObject
  • %11 = getelementptr inbounds <{ %swift.refcounted, %swift.refcounted*, %TSi }>, <{ %swift.refcounted, %swift.refcounted*, %TSi }>* %9, i32 0, i32 2 取出结构体的第三个元素 %TSi
  • %._value1 = getelementptr inbounds %TSi, %TSi* %11, i32 0, i32 0 获取值
  • store i64 %0, i64* %._value1, align 8 将值存到 %0 中也就是 amount
    综上分析可以得到之前的闭包结构中的 Box 变成了另外一种结构体
func makeIncrementer(amount: Int) -> () -> Int {
    var runningTotal = 10
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

struct ClosureData<Box>{
    var ptr: UnsafeRawPointer
    var object: UnsafePointer<Box>
}

struct HeapObject {
    var matedata: UnsafeRawPointer
    var refcount1: Int32
    var refcount2: Int32
}

struct Box<T1, T2>{
    var object: HeapObject
    var value1: UnsafePointer<T1> //第一个变量
    var value2: T2 //第二个变量
}

// 结构体是值类型 所以demoStruct 就是 f
struct demoStruct {
    var f:() -> Int
}
var f = demoStruct(f: makeIncrementer(amount: 15))

let ptr = UnsafeMutablePointer<demoStruct>.allocate(capacity: 1)
ptr.initialize(to: f)

// 内存属性绑定
let ctx = ptr.withMemoryRebound(to: ClosureData<Box<Int, Int>>.self, capacity: 1) {
    $0.pointee
}

print(ctx.ptr)
print(ctx.object.pointee.value1)
print(ctx.object.pointee.value2)
print("end")

打印结果:
0x0000000100002b40
0x0000000100727960
15

六:defer关键字

6.1 defer的概念

defer {} 里的代码会在当前代码块返回的时候执行,无论当前代码块是从哪个分支 return 的,即使程序抛出错误,也会执行。
如果有多个 defer 语句出现在同一作用域中,则它们出现的顺序与它们执行的顺序相反,也就是先出现的后执行。

func test() {
    defer { print("First defer") }
    defer { print("Second defer") }
    print("End of function")
}
test()

打印结果:
End of function
Second defer
First defer

6.2 defer的使用

func append(string: String, toFileAt url: URL) throws {
    let data = string.data(using: .utf8)!
    let fileHandle = try FileHandle(forUpdating: url)
    defer {
        fileHandle.closeFile()
    }
    
    if string.isEmpty {
        return
    }
    
    guard FileManager.default.fileExists(atPath: url.path) else {
        try data.write(to: url)
        return
    }
    
    fileHandle.seekToEndOfFile()
    fileHandle.write(data)
}

let url = URL(fileURLWithPath: NSHomeDirectory() + "/Desktop/swift.txt")
try append(string: "Swift", toFileAt: url)
try append(string: "Line 1", toFileAt: url)
try append(string: "Line 2", toFileAt: url)

使用 defer 关键字就不需要在每个判断里面将 fileHandle 进行关闭。

七:逃逸闭包

7.1 逃逸闭包的定义

当闭包作为一个实际参数传递给一个函数的时候,并且是在函数返回之后调用,我们就说这个闭包逃逸了。当我们声明一个接受闭包作为形式参数的函数时,你可以在形式参数前写 @escaping 来明确闭包是允许逃逸的。

  • 当闭包被当作属性存储,导致函数完成时闭包生命周期被延长
class Person {
    // 闭包作为属性存储
    var completionHandler: ((Int) -> Void)?
    
    func makeIncrementer(_ amount: Int, handler: @escaping ((Int) -> Void)) {
        var total = 10
        total += amount
        // 调用makeIncrementer传入的的闭包(handler) 赋值给属性
        // 这时闭包(handler)的生命周期比函数(makeIncrementer)的生命周期长
        // 闭包(handler) 可以在任何时候执行 取决于 completionHandler 什么时候调用
        self.completionHandler = handler
    }
    
    func function() {
        self.makeIncrementer(10) {
            print($0)
        }
    }
}

var p = Person()

p.function()

p.completionHandler?(10)
  • 当闭包异步执行,导致函数完成时闭包生命周期被延长
class Person {
    func makeIncrementer(_ amount: Int, handler: @escaping ((Int) -> Void)) {
        var total = 10
        total += amount
        // 主线程在运行过程中遇到了异步线程,继续执行主线程。这里闭包(handler)在异步线程中执行
        // 此时闭包(handler)的生命周期比函数(makeIncrementer)的生命周期长
        DispatchQueue.global().asyncAfter(deadline: .now() + 0.1) {
            handler(total)
        }
    }
    
    func function() {
        self.makeIncrementer(10) {
            print($0)
        }
    }
}
  • 可选类型的闭包默认是逃逸闭包
class Person {
    var completionHandler: ((Int) -> Void)?
    func makeIncrementer(_ amount: Int, handler: ((Int) -> Void)?) {
        var total = 10
        total += amount
        completionHandler?(total)
    }
}
可选类型的闭包默认是逃逸闭包.png

7.4 逃逸闭包与非逃逸闭包的区别

7.4.1 逃逸闭包
var handler: (() -> Void)?

func testEscaping(_ f: @escaping(() -> Void)) {
    handler = f
}

func test() -> Int {
    var age = 10
    testEscaping {
        age += 20
    }
    return age
}

var a = test()

通过 swiftc main.swift -emit-ir > ./main.ll 将代码编译成 IR 代码并且找到 test() 函数的位置

define hidden swiftcc i64 @"$s4main4testSiyF"() #0 {
entry:
  %age.debug = alloca %TSi*, align 8
  %0 = bitcast %TSi** %age.debug to i8*
  call void @llvm.memset.p0i8.i64(i8* align 8 %0, i8 0, i64 8, i1 false)
  %access-scratch = alloca [24 x i8], align 8
  %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) #2
  %2 = bitcast %swift.refcounted* %1 to <{ %swift.refcounted, [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** %age.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) #2
  call swiftcc void @"$s4main12testEscapingyyyycF"(i8* bitcast (void (%swift.refcounted*)* @"$s4main4testSiyFyycfU_TA" to i8*), %swift.refcounted* %1)
  call void @swift_release(%swift.refcounted* %1) #2
  %6 = bitcast [24 x i8]* %access-scratch to i8*
  call void @llvm.lifetime.start.p0i8(i64 -1, i8* %6)
  %7 = bitcast %TSi* %4 to i8*
  call void @swift_beginAccess(i8* %7, [24 x i8]* %access-scratch, i64 32, i8* null) #2
  %._value1 = getelementptr inbounds %TSi, %TSi* %4, i32 0, i32 0
  %8 = load i64, i64* %._value1, align 8
  call void @swift_endAccess([24 x i8]* %access-scratch) #2
  %9 = bitcast [24 x i8]* %access-scratch to i8*
  call void @llvm.lifetime.end.p0i8(i64 -1, i8* %9)
  call void @swift_release(%swift.refcounted* %1) #2
  ret i64 %8
}

%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) #2 我们可以看到这里创建了堆内存空间并且捕获了变量 age

7.4.2 非逃逸闭包
func testNoEscaping(_ f: () -> Void) {
    f()
}

func test() -> Int {
    var age = 10
    testNoEscaping {
        age += 20
    }
    return age
}

var a = test()
print(a)

这里的 testNoEscaping 就是非逃逸闭包我们可以通过 SIL 代码来确定

// testNoEscaping(_:)
sil hidden [ossa] @$s4main14testNoEscapingyyyyXEF : $@convention(thin) (@noescape @callee_guaranteed () -> ()) -> () {
// %0 "f"                                         // users: %2, %1
bb0(%0 : $@noescape @callee_guaranteed () -> ()):
  debug_value %0 : $@noescape @callee_guaranteed () -> (), let, name "f", argno 1 // id: %1
  %2 = apply %0() : $@noescape @callee_guaranteed () -> ()
  %3 = tuple ()                                   // user: %4
  return %3 : $()                                 // id: %4
} // end sil function '$s4main14testNoEscapingyyyyXEF'

可以看到一个关键字 @noescape 对于非逃逸闭包,再编译成 IR 代码

// test() 函数
define hidden swiftcc i64 @"$s4main4testSiyF"() #0 {
entry:
  %0 = alloca %TSi, align 8
  %1 = bitcast %TSi* %0 to i8*
  call void @llvm.memset.p0i8.i64(i8* align 8 %1, i8 0, i64 8, i1 false)
  %2 = bitcast %TSi* %0 to i8*
  call void @llvm.lifetime.start.p0i8(i64 8, i8* %2)
  %._value = getelementptr inbounds %TSi, %TSi* %0, i32 0, i32 0
  store i64 10, i64* %._value, align 8
  %3 = alloca i8, i64 24, align 16
  %4 = bitcast i8* %3 to %swift.opaque*
  %5 = bitcast %swift.opaque* %4 to <{ %swift.refcounted, %TSi* }>*
  %6 = getelementptr inbounds <{ %swift.refcounted, %TSi* }>, <{ %swift.refcounted, %TSi* }>* %5, i32 0, i32 1
  store %TSi* %0, %TSi** %6, align 8
  call swiftcc void @"$s4main14testNoEscapingyyyyXEF"(i8* bitcast (void (%swift.refcounted*)* @"$s4main4testSiyFyyXEfU_TA" to i8*), %swift.opaque* %4)
  %._value1 = getelementptr inbounds %TSi, %TSi* %0, i32 0, i32 0
  %7 = load i64, i64* %._value1, align 8
  %8 = bitcast %TSi* %0 to i8*
  call void @llvm.lifetime.end.p0i8(i64 8, i8* %8)
  ret i64 %7
}

这里可以看到这里没有 swift_allocObject 关键字也就是说这里并不会去开辟内存空间去捕获变量 age 而是直接获取 age 变量的值去使用。

7.4.3 非逃逸闭包的优点
  • 不会产生循环引用,函数作用域内释放
  • 编译器更多性能优化 (如没有 retain, relsase)
  • 上下文的内存保存在栈上,不是堆上
最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,100评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,308评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,718评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,275评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,376评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,454评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,464评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,248评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,686评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,974评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,150评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,817评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,484评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,140评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,374评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,012评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,041评论 2 351

推荐阅读更多精彩内容