Swift進階-閉包

Swift進階-類與結(jié)構(gòu)體
Swift-函數(shù)派發(fā)
Swift進階-屬性
Swift進階-指針
Swift進階-內(nèi)存管理
Swift進階-TargetClassMetadata和TargetStructMetadata數(shù)據(jù)結(jié)構(gòu)源碼分析
Swift進階-Mirror解析
Swift進階-閉包
Swift進階-協(xié)議
Swift進階-泛型
Swift進階-String源碼解析
Swift進階-Array源碼解析

一、函數(shù)類型

函數(shù)本身也有自己的類型,它由 形參類型和返回類型 組成。

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

//引用類型
var a: (Double, Double) -> Double = swift_add
print(a(10, 20))

var b = a
print(b(20 ,30))
斷點調(diào)試

上面調(diào)試結(jié)果可以看出來:

  • 函數(shù)類型是引用類型;
  • a指向的地址第一個8字節(jié)存儲的是函數(shù)類型。

函數(shù)類型是引用類型的案例:

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

let increm = makeIncrementer() // 引用類型去理解
print(increm()) // 11
print(increm()) // 12
print(increm()) // 13

// 理解成每次都在創(chuàng)建內(nèi)部函數(shù)對象
print(makeIncrementer()()) // 11
print(makeIncrementer()()) // 11
print(makeIncrementer()()) // 11

函數(shù)類型的元數(shù)據(jù)TargetFunctionTypeMetadata的源碼聲明:

/// 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();
  }
};
using FunctionTypeMetadata = TargetFunctionTypeMetadata<InProcess>;
  1. TargetFunctionTypeMetadata繼承自TargetMetadata說明它擁有Kind屬性(也就是isa);
  2. TargetFunctionTypeFlags<StoredSize> Flags; 作用是標識了函數(shù)的類型;
TargetFunctionTypeFlags
  1. ConstTargetMetadataPointer<Runtime, swift::TargetMetadata> ResultType;是返回值類型

4.其實TargetFunctionTypeMetadata還擁有一個參數(shù)列表,它其實是一個連續(xù)的內(nèi)存空間

parameters

于是乎可以分析出TargetFunctionTypeMetadata的數(shù)據(jù)結(jié)構(gòu):

struct TargetFunctionTypeMetadata{
    var kind: Int // isa
    var flags: Int 
    var resultType: UnsafeRawPointer // 返回值類型
    var arguments: ArgumentsBuffer<Any.Type> // 參數(shù)類型列表
    // 獲取參數(shù)個數(shù)
    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))
        }
    }
}

于是乎驗證一下推測來的數(shù)據(jù)結(jié)構(gòu)對不對:

let functionType = type(of: swift_add)

let functionPointer = unsafeBitCast(functionType as Any.Type, to: UnsafeMutablePointer<TargetFunctionTypeMetadata>.self)
print(functionPointer.pointee.numberArguments()) // 2
print(functionPointer.pointee.arguments.index(of: 0).pointee) // Double
let resultType = unsafeBitCast(functionPointer.pointee.resultType, to: Any.Type.self)
print(resultType) // Double

二、閉包

1.閉包概念

閉包是一個捕獲了上下文的常量/變量的函數(shù)。
可以看一個官方給的案例:

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

var a = makeIncrementer()

這里incrementer作為一個閉包,顯然他是一個函數(shù),其次為了保證其執(zhí)行,要捕獲外部變量runningTotal到內(nèi)部,所以閉包的關鍵就有捕獲外部變量或常量函數(shù)。

2.閉包表達式

閉包在語法上有這樣的標準結(jié)構(gòu): {(參數(shù)列表) -> 返回值 in 閉包體}

{ (param) -> (returnType) in
    //do somethings
}
  • 作用域(也就是大括號)
  • 參數(shù)和返回值
  • 函數(shù)體in之后的代碼

2.1 閉包即可以當做變量

// 也可以用可選性修飾  var closure: (String)->String = {...}
var closure = { (name: String) in
    return name
}
print(closure("安老師"))

2.2 閉包即可以當做函數(shù)的參數(shù)

// 如果用let修飾closure,那一旦賦值就不可改變
var closure = { (name: String) in
    return name
}

func call(_ closure: (String)->String) -> String {
    let name = closure("安老師")
    return name
}
print(call(closure))

2.3 閉包即可以當做函數(shù)的返回值

func makeIncrementer() -> () -> Int {
    var runningTotal = 10
    func incrementer() -> Int {
        runningTotal += 1
        return runningTotal
    }
    return incrementer
}
print(makeIncrementer()())
3.尾隨閉包

當我們把閉包表達式作為函數(shù)的最后一個參數(shù),如果當前的閉包表達式很長,我們可以通過尾隨 閉包的書寫方式來提高代碼的可讀性。

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

test(10, 20, 30, by: {(_ item1: Int, _ item2: Int, _ item3: Int) -> Bool in
   return (item1 + item2 < item3)
})

閉包表達式的好處 有很多:

  • 利用上下文推斷參數(shù)和返回值類型
  • 單表達式可以隱士返回,既省略 return 關鍵字
  • 參數(shù)名稱的簡寫(比如我們的 $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(by: {(item1, item2) in return item1 < item2 })
array.sort{(item1, item2) in item1 < item2 }
array.sort{ return $0 < $1 }
array.sort{ $0 < $1 }
array.sort(by: <)
4.捕獲值特性
  • Objective-CBlock 類型區(qū)分:全局、棧、堆;
  • swift閉包 都沒有這種概念了,var closure = { print("closure") } 中的closure存儲的是type metadata。

4.1 Objective-CBlock 類型
全局Block:不捕獲外部變量(只使用靜態(tài)變量、全局變量);
棧Block:捕獲局部變量、OC屬性;
堆Block:捕獲局部變量、OC屬性,并賦值給強引用(或copy修飾的變量)。

棧block與堆block的案例:

- (void)testBlock{
    NSObject *o = [NSObject new];
    NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)o)); // 1
    
    // 堆block (本身block在棧上,再賦值給了堆,所以o被引用2次)
    void(^strongBlock)(void) = ^{
        NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)o));
    };
    strongBlock(); // 3
    
    // 棧block 
    void(^__weak weakBlock)(void) = ^{
        NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)o));
    };
    weakBlock(); // 4

    // 堆block (由棧block轉(zhuǎn)化為堆block,o還被引用了1次,引用計數(shù)還得+1)
    void(^copyBlock)(void) =  [weakBlock copy];
    copyBlock(); // 5
}
// 1 3 4 5

4.2 block閉包在捕獲外部變量時的特性區(qū)別

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

如果想要外部被修改能夠影響當前 Block 內(nèi)部捕獲的值,我們只需要對當前的 i 添加 __block 修飾符:

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

把上面的代碼翻譯成swift代碼來看一下它的不一樣:

// swift是沒有__block修飾的
var i = 1
let closure = {
    print("closure \(i)")
}
i += 1

print("before closure \(i)") // 2
closure() // 2
print("after closure \(i)") // 2

區(qū)別:當閉包在使用 i 的時候,直接使用外部全局變量取 i 的值??纯?code>sil:

sil

上面的案例舉的全局變量案例,那如果把上面的代碼放到函數(shù)體里呢?

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

    print("before closure:\(i)") // 2
    closure() // 2
    print("after closure:\(i)") // 2
}

可以發(fā)現(xiàn),打印結(jié)果沒什么變化??赐?code>sil再下結(jié)論:

找到test聲明
找到closure

project_box與之對應的是alloc_boxalloc_box在捕獲的時候會在堆區(qū)上開辟內(nèi)存空間以存儲變量 i 的值;project_box是用到該值的時候,從堆上取出,再對其取值。
所以閉包在堆區(qū)開辟內(nèi)存空間的時候,有Metadata、Refcount 和 i。

總結(jié):
1.閉包是一個引用類型;
2.閉包捕獲全局(靜態(tài))變量/常量默認是不捕獲的,直接使用該變量/常量地址;
3.閉包捕獲局部值類型變量會在堆區(qū)開辟內(nèi)存空間;每次修改捕獲變量的值的時候,其實是修改堆區(qū)當中的值;
4.閉包捕獲局部引用類型變量,無需開辟堆區(qū)內(nèi)存空間,直接可引用堆區(qū)地址;并且該變量引用計數(shù)+1,在閉包生命周期結(jié)束會把該變量引用計數(shù)-1。

5.閉包的本質(zhì)

5.1 熟悉IR語法:
數(shù)組

[<elementnumber> x <elementtype>]   // 元素個數(shù) x 元素類型

//example:
alloca [24 x i8], align 8      24個i8都是0 

alloca [4 x i32] === array

結(jié)構(gòu)體

%swift.refcounted = type { %swift.type*, i64 }

//example:
%T = type {<type list>} //這種和C語言的結(jié)構(gòu)體類似

指針類型

<type> *

//example:
i64* //64位的整形

getelementptr 指令

struct munger_struct {
  int f1;
  int f2;
};

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

// 取出結(jié)構(gòu)體首地址
getelementptr inbounds %struct.munger_struct, %struct.munger_struct %1, i64 0 
// 1.取出結(jié)構(gòu)體首地址,2.然后取出結(jié)構(gòu)體第一個元素
getelementptr inbounds %struct.munger_struct, %struct.munger_struct %1, i32 0, i32 0

// 下面是一個案例:
int main(int argc, const char * argv[]) {
    int array[4] = {1, 2, 3, 4};
    int a = array[0];
    return 0;
}

其中 int a = array[0] 這句對應的LLVM代碼應該是這樣的:
a = getelementptr inbounds [4 x i32], [4 x i32]* array, i64 0, i32 0, i32 0

總結(jié):
第一個索引不會改變返回的指針的類型,也就是說ptrval前面的*對應什么類型,返回就是什 么類型
第一個索引的偏移量的是由第一個索引的值和第一個ty指定的基本類型共同確定的。
后面的索引是在數(shù)組或者結(jié)構(gòu)體內(nèi)進行索引
每增加一個索引,就會使得該索引使用的基本類型和返回的指針的類型去掉一層

5.2 閉包的數(shù)據(jù)結(jié)構(gòu)
把下面代碼編譯成IR,分析還原出閉包的數(shù)據(jù)結(jié)構(gòu)

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

let increm = makeIncrementer()

找到main函數(shù)

打開終端輸入,還原混寫名稱

$ xcrun swift-demangle s4main15makeIncrementerSiycyF
// ---------下面是輸出結(jié)果---------------
$s4main15makeIncrementerSiycyF ---> main.makeIncrementer() -> () -> Swift.Int
swift.refcounted的IR聲明

所以swift.refcounted是一個 {i64, i64}的結(jié)構(gòu)體

所以對于我們調(diào)用makeIncrementer()的返回值 { i8*, %swift.refcounted* } 就可以分析出閉包的最初始的數(shù)據(jù)結(jié)構(gòu)

struct ClosureData {
    var unkown: UnsafeRawPointer // i8*
    var closureHeapObject: ClosureHeapObject // swift.refcounted
}

struct ClosureHeapObject {
    var metadata: UnsafeRawPointer
    var refcount1: Int32
    var refcount2: Int32
}

接下來我們看看 s4main15makeIncrementerSiycyF就是makeIncrementer函數(shù)到底是怎么構(gòu)造出返回的東西 { i8*, %swift.refcounted* }的:

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堆空間的內(nèi)存地址
  %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
  // 做了一個指針類型的轉(zhuǎn)換,相當于UnsafeBitcast
  %2 = bitcast %swift.refcounted* %1 to <{ %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
  // 把它轉(zhuǎn)換成 %TSi* 類型  (%TSi* 其實是一個 type<{ i64 }>   64位的結(jié)構(gòu)體)
  %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
  // 把10這個值放到%._value偏移8字節(jié)的內(nèi)存空間里
  store i64 10, i64* %._value, align 8
  // 處理引用計數(shù)相關
  %5 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %1) #1
  call void @swift_release(%swift.refcounted* %1) #1
  // 將func里的內(nèi)嵌閉包地址轉(zhuǎn)換成 void * 插入到 { i8*, %swift.refcounted* } 的第一個元素
  %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
}

根據(jù)上面的IR代碼,還原出 ClosureData 的數(shù)據(jù)結(jié)構(gòu)為:

struct ClosureData<Box> {
    var ptr: UnsafeRawPointer // 閉包地址
    var box: UnsafePointer<Box>
}

struct Box<T> {
    var object: HeapObject // 實例對象的內(nèi)存地址
    var value: T 
}

///實例對象的內(nèi)存地址
struct HeapObject {
    var metadata: UnsafeRawPointer
    var refcount1: Int32
    var refcount2: Int32
}

結(jié)論:ClosureData 的數(shù)據(jù)結(jié)構(gòu) = 閉包的執(zhí)行地址 + 捕獲變量堆空間的地址

下面是驗證結(jié)論代碼:

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

struct NoMeanStruct{
    var closure: () -> Int
}

var closure = NoMeanStruct(closure: makeIncrementer())

let ptr = UnsafeMutablePointer<NoMeanStruct>.allocate(capacity: 1)
ptr.initialize(to: closure)

// 內(nèi)存綁定
let closure_ptr = ptr.withMemoryRebound(to: ClosureData<Box<Int>>.self, capacity: 1){ $0.pointee }

print("閉包地址: \(closure_ptr.ptr)")
print("實例對象的內(nèi)存地址: \(closure_ptr.box)")
print(closure_ptr.box.pointee.value)
print("end")
ptr.deinitialize(count: 3)
ptr.deallocate()
輸出結(jié)果

輸出的閉包地址0x0000000100005700一定能在Mach-O上面找到,所以可以使用下面的命令查找起來,可以證明輸出的地址就是閉包地址:

// 注意地址在Mach-O上是不帶0x的
$ nm -p 可執(zhí)行文件的路徑 | grep 0000000100005700 
// 輸出了一個混寫的名稱,把混寫名稱還原
$ xcrun swift-demangle 混寫的名稱 
// 最后得到下面的內(nèi)容
// $s11SwiftTest15makeIncrementerSiycyF11incrementerL_SiyFTA ---> partial apply forwarder for incrementer #1 () -> Swift.Int in SwiftTest.makeIncrementer() -> () -> Swift.Int

如果 makeIncrementer 有參數(shù)呢?那么Box的數(shù)據(jù)結(jié)構(gòu)就變了:

func makeIncrementer(_ amount: Int) -> ()->Int {
    var runningTotal = 10
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}
struct Box<T1, T2> {
    var object: HeapObject  // 把value1、value2統(tǒng)一當成object的屬性
    var value1: UnsafePointer<T1>
    var value2: T2
}

驗證Box數(shù)據(jù)結(jié)構(gòu)的改變

let funcStruct = NoMeanStruct(closure: makeIncrementer(20))
let func_ptr = UnsafeMutablePointer<NoMeanStruct>.allocate(capacity: 1)
func_ptr.initialize(to: funcStruct)

let closure_ptr = func_ptr.withMemoryRebound(to: ClosureData<Box<Int, Int>>.self, capacity: 1) { return $0.pointee }

print("閉包地址:\(closure_ptr.ptr)")
print("捕獲的地址:\(closure_ptr.box.pointee.value1)")
print("參數(shù)的值:\(closure_ptr.box.pointee.value2)")
print("end")
func_ptr.deinitialize(count: 3)
func_ptr.deallocate()

三、C與Swift的函數(shù)調(diào)用

新建一個對C文件.h和.c

#ifndef TestC_h
#define TestC_h

#include <stdio.h>
int TesctCFUnction(int (callBack)(int a, int b));

#endif /* TestC_h */
#include "TestC.h"

int TesctCFUnction(int (callBack)(int a, int b)){
    return  callBack(10, 20);
}

從下圖中就可以看出,該C文件聲明的函數(shù)是否有被倒入到Swift:

還需要再橋接文件(ProjectName-Bridging-Header.h)上倒入.h: #import "TestC.h";
在swift中就可以調(diào)用這個函數(shù):

let result = TesctCFUnction { $0 + $1 }
print(result) // 30

但是,如果給定一個已經(jīng)聲明的閉包作為參數(shù)呢?它會報錯!

image.png

這個時候需要用到 @convention 關鍵字,意思是把swift閉包轉(zhuǎn)換成c語言的回調(diào)函數(shù)

let closure: @convention(c)(Int32, Int32) -> Int32 = {(a: Int32, b: Int32) -> Int32  in
    return a + b
}
let result = TesctCFUnction(closure)
print(result) // 30

四、逃逸閉包、非逃逸閉包與自動閉包

1.逃逸閉包

逃逸閉包的定義:
當閉包作為一個實際參數(shù)傳遞給一個函數(shù)的時候,并且是在函數(shù)返回之后調(diào)用,我們就說這個閉包逃逸了。當我們聲明一個接受閉包作為形式參數(shù)的函數(shù)時,你可以在形式參數(shù)前寫 @escaping 來明確閉包是允許逃逸的。

  • 作為函數(shù)的參數(shù)傳遞
  • 當前閉包在函數(shù)內(nèi)部異步執(zhí)行或者被存儲
  • 函數(shù)結(jié)束,閉包被調(diào)用,生命周期結(jié)束

情況一:

// 用屬性去存儲閉包
class Teacher {
    var completion: ((Int)->Void)?
    
    func makeIncrement(_ hander: @escaping (Int)->Void) {
        self.completion = hander
    }
    
    func doSomething() {
        makeIncrement() { result in
            print(result)
        }
    }
}

let t = Teacher()
t.doSomething()
t.completion?(10)

情況二:

// 逃逸閉包二
class Teacher {
    func makeIncrement(_ hander: @escaping (Int)->Void) {
        DispatchQueue.global().asyncAfter(deadline: .now()+0.5) {
            hander(10)
        }
    }

    func doSomething() {
        makeIncrement() { result in
            print(result)
        }
    }
}

let t = Teacher()
t.doSomething()

情況三:

class Teacher {
    var completion: ((Int)->Void)?
    // 可選型的閉包是隱式逃逸閉包(sil也不能看出來的)
    func makeIncrement(_ hander: ((Int)->Void)?) {
        self.completion = hander
    }
    
    func doSomething() {
        makeIncrement() { result in
            print(result)
        }
    }
}
2.非逃逸閉包

除非標記了@escaping 或者可選型閉包,系統(tǒng)默認都是非逃逸閉包。它的聲明周期是確定的(函數(shù)調(diào)用完,那閉包的聲明周期就結(jié)束了),那就意味著非逃逸閉包捕獲的局部變量無需在堆區(qū)開辟內(nèi)存空間

  • 不會產(chǎn)生循環(huán)引用,
  • 閉包所在的函數(shù)作用域內(nèi)釋放
  • 編譯器更多性能優(yōu)化 (沒有retain / release)
  • 上下文的內(nèi)存保存再棧上,不是堆上
func testNoEscaping(_ f: (() -> Void)?){
    f?()
}

func test() -> Int{
    var age = 20
    
    testNoEscaping {
        age += 30
    }
    
    return age
}
print(test()) // 50

編譯成IR代碼:

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) #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** %age.debug, align 8
  %._value = getelementptr inbounds %TSi, %TSi* %4, i32 0, i32 0
  store i64 20, i64* %._value, align 8
  %5 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %1) #1
  %6 = ptrtoint %swift.refcounted* %1 to i64
  call swiftcc void @"$s4main14testNoEscapingyyyycSgF"(i64 ptrtoint (void (%swift.refcounted*)* @"$s4main4testSiyFyycfU_TA" to i64), i64 %6)
  call void @"$sIeg_SgWOe"(i64 ptrtoint (void (%swift.refcounted*)* @"$s4main4testSiyFyycfU_TA" to i64), i64 %6)
  %7 = bitcast [24 x i8]* %access-scratch to i8*
  call void @llvm.lifetime.start.p0i8(i64 -1, i8* %7)
  %8 = bitcast %TSi* %4 to i8*
  call void @swift_beginAccess(i8* %8, [24 x i8]* %access-scratch, i64 32, i8* null) #1
  %._value1 = getelementptr inbounds %TSi, %TSi* %4, i32 0, i32 0
  %9 = load i64, i64* %._value1, align 8
  call void @swift_endAccess([24 x i8]* %access-scratch) #1
  %10 = bitcast [24 x i8]* %access-scratch to i8*
  call void @llvm.lifetime.end.p0i8(i64 -1, i8* %10)
  call void @swift_release(%swift.refcounted* %1) #1
  ret i64 %9
}

testNoEscaping捕獲了age并沒有在堆區(qū)開辟內(nèi)存空間。

3.自動閉包

自動閉包的定義:
是一種用來把實際參數(shù)傳遞給函數(shù)表達式打包的閉包,不接受任何實際參數(shù),當其調(diào)用是,返回內(nèi)部表達式的值。

好處:用普通表達式代替閉包的寫法,語法糖的一種

func debugOutPrint(_ condition: Bool , _ message: @autoclosure () -> String){
    if condition {
        print("lg_debug:\(message())")
    }
}


func dosomthing() -> String{
    //耗時的操作
    return "Application Error Occured"
}

//String, () -> String
// {} -> String
debugOutPrint(true, dosomthing)
debugOutPrint(true, { return dosomthing() })

message參數(shù)既可以接收String,也可以接收 ()->String

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容