iOS逆向Swift探索

Swift探索(七): 闭包

2022-04-15  本文已影响0人  Lee_Toto

一:函数类型

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

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
}

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 语法。使用闭包表达式能更简洁的传达信息。当然闭包表达式的好处有很多:

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位的整形
%2 = bitcast i8** %1 to i8*

指针类型的转换

<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 获取第一个结构体成员

总结

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 函数中

struct ClosureData {
    var unkown: UnsafeRawPointer
    var refcount: RefCount
}

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

那么这里具体存储的是什么呢?定位到 $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
}

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 } 并且

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 非逃逸闭包的优点
上一篇下一篇

猜你喜欢

热点阅读