Function types

In Swift, functions have the same status as other data types. Functions can not only be assigned to other variables, but can also be passed as arguments to another function, or as a return value from another function. So functions have their own types, and when we use a function as a variable, if it has the same name, then when you specify the type of the function, the compiler will not give an error.

When a function is assigned to a variable, let’s see what’s in that variable?As you can see, a function type is also a reference type and, like any other data structure, has its own Metadata. We can explore from inside the source code.Through the source code we can know,TargetFunctionTypeMetadataInherited fromTargetMetadataSo, there will be aKindProperties. At the same time, it has anotherFlagsProperty and identifies the type of return valueResultTypeProperties. In addition, the class has a contiguous memory array space for the argument list. As you can see, the parameter list contains allTargetMetadataType. That is, the parameter type and return value type inside the function areAny.TypeType.

Next we’ll look at the TargetFunctionTypeFlags class, which stores the flag bits of function types. You can get the number of function arguments in this class.

class TargetFunctionTypeFlags {

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;
public:
unsigned getNumParameters(a) const { return Data & NumParametersMask; }
Copy the code

From the above code we can get this TargetFunctionTypeMetadata structure

struct TargetFunctionTypeMetadata {
    var kind: Int
    var flags: Int
    var resultType: Any.Type
    var arguments:ArgumentsBuffer<Any.Type>
    
    func numberArguments(a) -> 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))
        }
    }
}
Copy the code

Next, we use the above data structure to get the number of arguments, return values, and argument types of a function.

func addTwoInts(_ a: Int._ b: String) -> Bool {
    return true
}

let value = type(of: addTwoInts)

let functionType = unsafeBitCast(value as Any.Type to: UnsafeMutablePointer<TargetFunctionTypeMetadata>.self)

let numberOfArguments = functionType.pointee.numberArguments()

print("This function\(value)\(numberOfArguments)The parameters")

let returnType = functionType.pointee.resultType

print("This function\(value)Is the return type of\(returnType)")

for i in 0..<numberOfArguments {
    let argumentType = functionType.pointee.arguments.index(of: i).pointee
    print("This function\(value)The first\(i+1)The type of the parameter is\(argumentType)")}// Print the resultThe function (Int.String) - >Bool2The function (Int.String) - >BoolIs the return type ofBoolThe function (Int.String) - >BoolThe first1The type of the parameter isIntThe function (Int.String) - >BoolThe first2The type of the parameter isString
Copy the code

closure

What is a closure

A closure is a function that captures a constant or variable in the context. Let’s take a look at the official example.

func makeIncrementer(a)- > () - >Intvar runningTotal = 10 
    func incrementer(a) -> Int { 
        runningTotal + = 1 
        return runningTotal 
    } 
    return incrementer 
}
Copy the code

In the code, Incrementer acts as a closure as well as a function. And incrementer has a longer lifetime than makeIncrementer. When makeIncrementer completes execution, the variable runningTotal contained within it also disappears, but Incrementer may not have been executed. In order for Incrementer to execute, it is necessary to capture runningTotal inside incrementer, so there are two key points to make a closure, one is the function, and the other is the ability to capture external variables or constants.

Closure expression

In Swift, we can define closures with the following expression

{ (param) -> (returnType) in 

//do something

}
Copy the code

As you can see, closure expressions consist of scope, function parameters, return values, the keyword in, and the function body.

Closure expression syntax can use constant formal parameters, variable formal parameters, and input/output formal parameters, but cannot provide default values. Variable formal parameters can also be used, but they need to be used at the end of the formal parameter list. Tuples can also be used as formal parameters and return types.

The whole function part of a closure is imported by the keyword IN, which indicates that the closure’s formal parameter types and return types have been defined and that the closure’s function body is about to begin.

Use of closures

In Swift, closures can be used as variables or passed as parameters to functions.

  • Closures as variables
var closure : (Int) - >Int = { (age: Int) in 
    return age 
}
Copy the code
  • The closure is declared as an optional type
var closure : ((Int) - >Int)? 
closure = nil
Copy the code
  • Closure as a constant (cannot be changed once assigned)
let closure: (Int) - >Int 

closure = {(age: Int) in 
    return age 
}
Copy the code
  • Closures as function arguments
func test(param: () - >Int)print(param()) 

}

var age = 10 

test { () -> Int in 
    age + = 1 
    return age
}
Copy the code

Following the closure

When we take a closure expression as the last argument to a function, if the current closure expression is long, we can improve the readability of the code by following the closure in the way it is written.

Let’s first define a function

func test(_ a: Int._ b: Int._ c: Int.by: (_ item1: Int._ item2: Int._ item3: Int) - >Bool) -> Bool{
    
    return by(a, b, c)
}
Copy the code
  • Trailing closures are not used
test(10.20.30, by: {(_ item1: Int._ item2: Int._ item3: Int) - >Bool in
    return (item1 + item2 < item3)
})
Copy the code
  • Use trailing closures
test(10.20.30) {(_ item1: Int._ item2: Int._ item3: Int) in
    return item1 + item2 < item3
}
Copy the code

Short for closure expression

In Swift, closure expressions are used to convey information more succinctly. But don’t be too brief.

var array = [1.2.3]
array.sort(by: {(item1 : Int, item2: Int) - >Bool in return item1 < item2 })
Copy the code
  • Use context to infer parameter and return value types
array.sort(by: {(item1, item2) -> Bool in return item1 < item2 }) 

array.sort(by: {(item1, item2) in return item1 < item2 })
Copy the code
  • A single expression can be hermitage returned, both omittedreturnThe keyword
array.sort{(item1, item2) in item1 < item2 }
Copy the code
  • Shorthand for parameter names (e.g$0)
array.sort{ return $0 < The $1 }
array.sort{ $0 < The $1 }
Copy the code
  • Trailing closure expression
array.sort(by: <)
Copy the code

Closures capture values

The closure captures a global variable

Let’s first look at the closure capturing global variables as follows

var i = 1

let closure = {
    print("closure\(i)")
}

i + = 1

print("before closure \(i)")

closure()

print("after closure \(i)")

// Print the result
before closure 2

closure2

after closure 2
Copy the code

As you can see,iAfter the value of is changed,closureThe inside of theiChanges have taken place. And OCblockVery like. Let’s explore this through the SIL file. We can see from the above sil source code that when executed toclosureWhen it comes to closures, go straight to finding variablesiAnd then put theiI’m going to pull it out. At a time wheniThe value of has changed, so extractiThe value of PI is 2.

That is, the closure is taking the global variable directly to modify its value, so it should not be called global variable capture, because there is no additional operation on the global variable.

The closure captures a local variable

What happens internally when a closure captures a local variable? Let’s take an official example.

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

let makeInc = makeIncrementer()
Copy the code

Then we convert it to a sil file and see the makeIncrementer() function formed as follows:

As you can see, inmakeIncrementer()Function, using thealloc_box, while in the closureincrementer()“Is used againproject_box. Let’s go to the official documentation for the definitions of these two commands. 在The official documentationAs you can see,alloc_boxIs to create instance objects in heap space,project_boxRetrieves the value from the instance object address. So when a closure captures a variable, it creates an instance object in the heap space and stores the value of the captured variable in that instance object. Each call to the closure uses the address of the instance variable in the same heap space, so changing the value outside the closure changes the value inside the closure.Print through LLDBmakeInc, we can also see inside closuresMetadata

The nature of closures

This time we need to explore the nature of closures by analyzing IR files, starting with IR syntax

IR grammar

Void *, i16: Int16, i32: Int32, i64: Int64, void *

  • An array of
[< elementNumber > x < elementType >] //example alloc [24 x i8], align 8 24 i8 are 0 alloc [4 x i32] === arrayCopy the code
  • The structure of the body
%T = type {<type list>} // Swift. Refcount struct has two members, swift. Type * and i64. %swift.refcount = type {%swift.type*,i64}Copy the code
  • Pointer to the
<type> * // int64-bit integer pointer i64*Copy the code
  • getelementptr

To get a member of an array or structure in LLVM, the syntax is as follows

<result> = getelementptr <ty>, <ty>* <ptrval>{, [inrange] <ty> <id x>}

<result> = getelementptr inbounds <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}
Copy the code

Let’s use examples to further illustrate

struct munger_struct{ int f1; int f2; }; I64 0 (munger_struct, munger_struct, munger_struct); Getelementptr inbounds %struct.munger_struct, %struct.munger_struct %1, getelementptr inbounds %struct. I64 0; // munger_struct; // munger_struct; Getelementptr inbounds %struct.munger_struct, getelementptr inbounds %struct.munger_struct, getelementptr inbounds %struct. Struct. Munger_struct %1, i64 0, i32 0 Getelementptr inbounds %struct.munger_struct, getelementptr inbounds %struct. Munger_struct, getelementptr inbounds %struct. %struct.munger_struct %1, i64 0, i32 1 int main(int argc, const char * argv[]) { int array[4] = {1, 2, 3, 4}; int a = array[0]; return 0; } int a = array[0] A = Getelementptr inbounds [4 x i32], [4 x i32]* array, I64 0, i32 0 - [4 x i32]* array: Returns a pointer to an array. - First 0: pointer to the first address of the array. - Second 0: the offset from the array element, the first member variable.Copy the code

For Getelementptr, we can conclude the following:

  • The first index does not change the type of the pointer returned. That is, the * before ptrval corresponds to the type of the pointer returned

  • The offset of the first index is determined by the value of the first index and the base type specified by the first TY.

  • The second index and subsequent indexes are indexed within an array or structure body

  • Each additional index removes a layer from the base type used by the index and the type of the pointer returned.

The nature of closures

Next, we use IR to analyze the closure as follows:

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

    return incrementer
}

var makeInc = makeIncrementer()
Copy the code

After converting to IR file, we captured some code as follows:

%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}> 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 }Copy the code

From the code, we can see that inside main, the first call is mademakeIncrementer()Function, which returns a{ i8*, %swift.refcounted* }The structure of, and%swift.refcountedIt’s also a structure{ %swift.type*, i64 }And the%swift.typeanother{ i64 }Structure that contains a variable of 64-bit integer type.

So in main, we’re actually doing something like this. MakeIncrementer () is called to return a struct, then fetch all the member variables of the struct, and then create some memory for the makeInc variable {i8*, % swif.refcounted *}, but some of the variables are always counted. The removed member variables are stored in the memory space, which completes an assignment operation.

Let’s take a look at the IR code for the makeIncrementer() function and see what’s going on inside it.

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
}
Copy the code

Let’s look at this code in detail:

%runningTotal.debug = alloca %TSi*, align 8
%0 = bitcast %TSi** %runningTotal.debug to i8*
Copy the code

Create a %TSi* structure for the local variable runningTotal, which holds the value of the local variable, and use a pointer of type I8 to point to the structure.

%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
Copy the code

Counted * this code creates an instance variable of type %swift.refcounted* in the heap space.

%2 = bitcast %swift.refcounted* %1 to <{ %swift.refcounted, [8 x i8] }>*
Copy the code

Counted * converts a %swift.refcounted* pointer to the {%swift.refcounted, [8 x i8]} structure.

%3 = getelementptr inbounds <{ %swift.refcounted, [8 x i8] }>, <{ %swift.refcounted, [8 x i8] }>* %2, i32 0, i32 1
Copy the code

The code actually points to the element with index 1 of the <{%swift.refcounted, [8 x i8]}> structure. That’s the [8 x i8] array.

%4 = bitcast [8 x i8]* %3 to %TSi*
store %TSi* %4, %TSi** %runningTotal.debug, align 8
Copy the code

This code converts a pointer to array [8 x i8] into a %TSi* pointer and stores the %TSi* structure of the local variable runningTotal into the array.

%._value = getelementptr inbounds %TSi, %TSi* %4, i32 0, i32 0
store i64 10, i64* %._value, align 8
Copy the code

This code fetches the first element of the [8 x i8] array, the %TSi* structure of the local variable runningTotal, and stores the value 10 into the structure.

%5 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %1) #1
call void @swift_release(%swift.refcounted* %1) #1
Copy the code

This code is all about reference counting, no research.

%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
Copy the code

The code above is just pointing toincrementer()The pointer to the function becomesi8*And save the data created above10the%swift.refcounted*Type with the instance variable inserted into{ i8*, %swift.refcounted* }In the structure. And I’m going to return this structure out.

Based on the above analysis, we can express the closure structure in memory as the following structure:

Struct ClosureData{var PTR: UnsafeRawPointer Struct HeapObject {var matedata: UnsafeRawPointer var refcount1: Int32 var refcount2: Int32 }Copy the code

We saw in SIL analysis that the closure used alloc_box to create instance variables, and we knew that the value of the captured variable was stored in an instance variable of type %swift.refcounted* in the closure. Therefore, we can further restore the object property variable

struct Box<T>{
    var object: HeapObject
    var value: T
}
struct HeapObject {
    var matedata: UnsafeRawPointer
    var refcount1: Int32
    var refcount2: Int32
}
Copy the code

So the closure’s final memory structure can be expressed like this:

Struct ClosureData{var PTR: UnsafeRawPointer Struct Box<T>{var object: HeapObject var value: T } struct HeapObject { var matedata: UnsafeRawPointer var refcount1: Int32 var refcount2: Int32 }Copy the code

Verify the closure structure analysis results

Let’s verify that the above structure is correct. The verification code is as follows:

struct NoMeanStruct {
    var f: () -> Int
}
var f = NoMeanStruct(f: makeIncrementer())
let ptr = UnsafeMutablePointer<NoMeanStruct>.allocate(capacity: 1)
ptr.initialize(to: f)

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

print("Closure function memory address:\(ctx.ptr)")
print("Closure heapspace address:\(ctx.object)")
print("Closure function capture variable value:\(ctx.object.pointee.value)")

// Print the resultClosure function memory address:0x0000000100008c80Closure heapspace address:0x0000000100748aa0The value of the closure's capture variable:10
Copy the code

Now let’s verify this resultThe result is exactly the same as the inference.

Capturing reference types

What if the closure captures a variable type that is a reference type, such as an instance object of a class? Let’s start with an example

class LGTeacher {
    var age = 10
}

func test(a) {
    var t = LGTeacher(a)let clousure = {
        t.age + = 10
    }    
    clousure()
}

test()
Copy the code

Convert to IR code, and let’s see what’s going on inside main

define hidden swiftcc void @"$s4main4testyyF"() #0 { entry: %0 = alloca %T4main9LGTeacherC*, align 8 %1 = bitcast %T4main9LGTeacherC** %0 to i8* call void @llvm.memset.p0i8.i64(i8* align 8 %1, i8 0, i64 8, I1 false) // Create a %swift.function structure %clousure.debug = alloca %swift.function, align 8 %2 = bitcast %swift.function* %clousure.debug to i8* call void @llvm.memset.p0i8.i64(i8* align 8 %2, i8 0, i64 16, i1 false) %3 = bitcast %T4main9LGTeacherC** %0 to i8* call void @llvm.lifetime.start.p0i8(i64 8, i8* %3) %4 = call swiftcc %swift.metadata_response @"$s4main9LGTeacherCMa"(i64 0) #7 %5 = extractvalue % swift.metadata_Response %4, 0 // the lgteacher.__allocating_init () method is called, which creates an instance object t and stores the address of the instance object in register %6. %6 = call swiftcc %T4main9LGTeacherC* @"$stmain9LGTeacherCACycfC"(%swift.type* swiftself %5) %7 = bitcast %T4main9LGTeacherC* %6 to %swift.refcounted* %8 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %7) #3 store %T4main9LGTeacherC* %6, %T4main9LGTeacherC** %0, align 8 %9 = bitcast %T4main9LGTeacherC* %6 to %swift.refcounted* %10 = bitcast %swift.function* %clousure.debug to i8* call void @llvm.lifetime.start.p0i8(i64 16, i8* %10) %clousure.debug.fn = getelementptr inbounds %swift.function, % swif. function* %clousure. Debug, i32 0, i32 0 // Store the address of the test function in %clousure. store i8* bitcast (void (%swift.refcounted*)* @"$s4main4testyyFyycfU_Tf2i_nTA" to i8*), i8** %clousure.debug.fn, align 8 %clousure.debug.data = getelementptr inbounds %swift.function, %swift.function* %clousure.debug, i32 0, I32 1 // Save the address of the instance variable t to %clousure.debug.data, because the address of the instance variable t is already in the heap, it does not need to be rebuilt. store %swift.refcounted* %9, %swift.refcounted** %clousure.debug.data, Align 8 // Counted instance object reference operation %11 = call % swif.refcounted * @swift_retain(% swif.refcounted * returned %9) #3 // Clousure closure was called call swiftcc void @"$s4main4testyyFyycfU_Tf2i_nTA"(%swift.refcounted* swiftself %9) call void @swift_release(%swift.refcounted* %9) #3 call void @swift_release(%swift.refcounted* %9) #3 %toDestroy = Load %T4main9LGTeacherC*, %T4main9LGTeacherC** %0, counted instance variable t into the %swift. Refcounted structure of the closure. align 8 call void bitcast (void (%swift.refcounted*)* @swift_release to void (%T4main9LGTeacherC*)*)(%T4main9LGTeacherC*  %toDestroy) #3 %12 = bitcast %T4main9LGTeacherC** %0 to i8* call void @llvm.lifetime.end.p0i8(i64 8, i8* %12) ret void }Copy the code

When you capture a reference type, you don’t need to capture the instance object, because it is already in the heap, so you don’t need to create an instance of the heap space, just store its address in the closure structure, and operate on the reference count of the instance object.

Let’s verify this using the closure structure above

class LGTeacher {
    var age = 10
}

func test(a) {
    let t = LGTeacher(a)let clousure = {
        t.age + = 10
    }
    
    let f = NoMeanStruct(f: clousure)
    let ptr = UnsafeMutablePointer<NoMeanStruct>.allocate(capacity: 1)
    ptr.initialize(to: f)

    let ctx = ptr.withMemoryRebound(to: ClosureData<Box<Int> >.self capacity: 1) {
        $0.pointee
    }

    print("Closure function memory address:\(ctx.ptr)")
    print("Closure heapspace address:\(ctx.object)")
    print("Closure function capture variable value:\(ctx.object.pointee.value)")
    print("end")
}

test()
Copy the code

The print result is as follows:The heapspace address stored by the closure is the address of the captured instance variable of the reference type.

Closures capture multiple variables

The closure captured one variable. What if the closure captured multiple variables? We continue to analyze the case of the two variables as follows:

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

var makeInc = makeIncrementer(10)
Copy the code

Next we compile it into an IR file. The IR code for the makeIncrementer() function looks like this:

define hidden swiftcc { i8*, %swift.refcounted* } @"$s4main15makeIncrementerySiycSiF"(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*)* @"$s4main15makeIncrementerySiycSiF11incrementerL_SiyFTA" to i8*), %swift.refcounted* undef }, %swift.refcounted* %8, 1
  ret { i8*, %swift.refcounted* } %12
}
Copy the code

Now let’s break it down one by one:

%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
Copy the code

The above code creates a memory space for the captured variables amount and runningTotal in registers %1 and %2, respectively. We then put 0 in the variable amount, because amount is passed in from the outside and we don’t know how much value is passed, so the default value is 0.

%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
Copy the code

Here, as in the case of the single variable being captured above, create a structure of %swift.refcounted* and store the runningTotal memory address there, plus 10 to the runningTotal memory address.

%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 }>*
Copy the code

There’s another new % swif.refcounted * structure, and the pointer points to the {% swif.refcounted, % swif.refcounted *, %TSi} structure.

%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
Copy the code

The % swif.refcounted * structure containing the runningTotal variable is returned. Counted * of the {%swift.refcounted*, %swift.refcounted*, %TSi} structure’s second %swift.refcounted* attribute and discharged its reference count. The amount variable is also stored in %TSi.

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

The incrementer() function’s memory address and Pointers to the {%swift.refcounted, %swift.refcounted*, %TSi} structure make up the closure’s data structure.

To sum up, the first captured value is stored in the heap, a new object is created after the second value is captured, and the first object is stored in the new object.

So, when the closure captures two variables, its in-memory data structure is represented as follows:

struct ClosureParamData<T>{
    var ptr: UnsafeRawPointer // Function address
    var captureValue: UnsafePointer<T>// Store the value that captures the heap space address
}

struct TwoParamerStruct<T1.T2>{
    var object: HeapObject
    var value1: UnsafePointer<Box<T1>>
    var value2: T2
}

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

struct HeapObject {
    var matedata: UnsafeRawPointer
    var refcount1: Int32
    var refcount2: Int32
}
Copy the code

Next we restore the memory structure of the makeIncrementer() function.

let f = NoMeanStruct(f: makeIncrementer(20))

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

ptr.initialize(to: f)

let ctx = ptr.withMemoryRebound(to: ClosureData<TwoParamerStruct<Int.Int> >.self, capacity: 1) {
    $0.pointee
}

print("Closure function memory address:\(ctx.ptr)")
print("Closure heapspace address:\(ctx.captureValue)")
print("The closure function's first captured variable value:\(ctx.captureValue.pointee.value1.pointee.value)")
print("The second value of the closure's capture variable:\(ctx.captureValue.pointee.value2)")

// Print the resultClosure function memory address:0x0000000100008660Closure heapspace address:0x000000010113c430The closure function's first captured variable value:10The second value of the closure's capture variable:20
Copy the code

Closures capture three variables

Let’s look at the closure capturing three variables as follows:

func makeIncrementer(_ amount: Int, _ amount1: Int) -> () -> Int { var runningTotal = 10 func incrementer() -> Int { runningTotal += amount runningTotal += amount1 Return runningTotal} return incrementer} var makeInc = makeIncrementer(20,30)Copy the code

After compiling into IR,makeIncrementer()The code for the function is as followsAs you can see, the code is almost the same as that for capturing two variables, except that the structure becomes{ %swift.refcounted, %swift.refcounted*, %TSi, %TSi }One too many%TSiUsed to store the value of the third variable captured.

Closures capture multiple variable summaries

From the analysis of two and three variables captured by closures above, we can see that the closure data structure can capture a single variable and multiple variables. So we restore the closure data structure as follows:

Struct ClosureData<Box>{var PTR: UnsafeRawPointer UnsafePointer<Box> struct ClosureData<MutiValue>{var PTR: UnsafeRawPointer // function address var captureValue: UnsafePointer<MutiValue> // Stores the value that captures the heap space address} struct MutiValue<T1,T2...... >{ var object: HeapObject var value: UnsafePointer<Box<T1>> var value: T2 var value: T3 ..... } struct Box<T>{ var object: HeapObject var value: T } struct HeapObject { var matedata: UnsafeRawPointer var refcount1: Int32 var refcount2: Int32 }Copy the code

Escape a closure

Escape closure definition: When a closure is passed to a function as an actual argument and is called after the function returns, the closure is said to have escaped. When we declare a function that accepts closures as formal arguments, you can make it clear that closures are allowed to escape by writing @escaping before the formal argument.

Using escape closures generally meets the following two conditions:

  • When closures are stored as properties, the closure life is prolonged when the function completes

  • When a closure executes asynchronously, the closure life cycle is prolonged when the function completes.

  • Closures of alternative types are escape closures by default.

The following closure is also an escape, for the compiler to assign a closure to a variable that the compiler thinks might be executed somewhere else.

So, the conditions required to escape closure:

  • Passed as an argument to a function.
  • The current closure is executed asynchronously or stored inside the function.
  • The function ends, the closure is called, and the closure’s life cycle does not end.

Automatic closure

@AutoClosure is an automatically created closure that wraps parameters as closures. This closure takes no arguments and returns the value passed in when it is called. This convenience syntax lets you omit the curly braces of a closure when calling it

Function closure () -> Any (); @autoclosure (){return a});} closure (){return a}

func debugOutPrint(_ condition: Bool , _ message: @autoclosure () -> String){ if condition { print("debug:(message())") } } debugOutPrint(true,"Application Error Occured"  ) debugOutPrint(true, getString ) func getString()->String{ return "Application Error Occured" }Copy the code

Defer the keyword

The code in defer {} will be executed when the current code block returns, regardless of which branch it returned from, even if the application throws an error.

If multiple defer statements appear in the same scope, they appear in the reverse order of their execution, i.e., first and then executed.

Take a simple example

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

f()
// Print the result
End of function
Second defer
First defer
Copy the code