This is the 18th day of my participation in the August Challenge

This paper mainly analyzes escaping closure, non-escaping closure and automatic closure

Escape closure & non-escape 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 declaring a function that accepts closures as formal arguments, @escaping can be written before the formal argument to make it clear that closures are allowed to escape

  • After decorating the closure with @escaping, we must show that we use self in the closure

  • After Swift3.0, the system default closure argument is @nonescaping, which can be verified with SIL

– 1. Execution time: execute in the function body

- 2. Closure life cycle: After the function is executed, the closure is goneCopy the code

Two invocation cases of escape closures

  • 1. Delay the call

  • 2. Store it as a property and call it later

1. As an attribute

When a closure is used as a storage property, the following considerations apply:

  • Define a closure property

  • 2. Assign the closure property in the method

  • 3. Call at the right time (business logic related)

As shown below, the current complitionHandler, as a CJLTeacher property, is called after the makeIncrementer method has been called, in which case the closure lifetime is longer than the current method lifetime

Var complitionHandler: ((Int)->Void)? // Function argument with @escaping modifier, which allows a function to return and then call func makeIncrementer(amount: Int, handler: @escaping (Int)->Void){var runningTotal = 0 runningTotal += amount // Assign to attribute self.plitionHandler = handler} func doSomething(){ self.makeIncrementer(amount: 10) {print($0)}} deinit {print("CJLTeacher deinit")} t.complitionHandler?(10) <! Print the result --> 10Copy the code

2. Delay the call

  • [Used in the delay method]1, inDelay methodTo call the escape closure
Class CJLTeacher {// define a closure attribute var complitionHandler: ((Int)->Void)? // Function argument with @escaping modifier, which allows a function to return and then call func makeIncrementer(amount: Int, handler: @escaping (Int)->Void){var runningTotal = 0 runningTotal += amount // Assign value to attribute self.plitionHandler = handler // Delay call DispatchQueue.global().asyncAfter(deadline: .now()+0.1) {print(" escape closure delayed execution ") handler(runningTotal)} print(" function finished ")} func doSomething(){ self.makeIncrementer(amount: 10) {print($0)}} deinit {print("CJLTeacher deinit")} -- Print the result --> The function completes the escape closure and delays execution 10Copy the code

The current method execution does not wait for the closure to complete, but returns directly, so the current closure life cycle is longer than the method

Escape closures vs non-escape closures

  • Non-escape closure: A function that takes a closure as an argument that is called before the function terminates, i.e. the closure is called before the function’s scope ends

    • Circular references are not generated because the scope of the closure is within the scope of the function, and all objects captured by the closure are freed after the function completes execution

    • 2. For non-escape closures, the compiler optimizes by omitting memory management calls

    • Context captured by a non-escape closure is stored on the stack, not on the heap (official documentation). Note: there is no verification for this point at present, there is verification of children’s shoes welcome to leave a message

  • Escape closure: A function that takes a closure as an argument. The escape closure may not be called until the function returns, i.e. the closure has escaped the scope of the function

    • 1. Loop references are possible because the escape closure requires an explicit reference to self, and self may hold closure variables (similar to loop references to OC blocks).

    • 2. Typically used for asynchronous function returns, such as network requests

  • Usage advice: It is good to use non-escape closures in development if you don’t have a specific need for memory optimization, so Apple divides closures into two types and uses escape closures for special cases

Automatic closure

Here is an example where an error message is printed when condition is true, that is, if false, the current condition is not executed

If condition is false, func debugOutPrint(_ condition: Bool, _ message: String){ if condition { print("cjl_debug: \(message)") } } debugOutPrint(true, "Application Error Occured")Copy the code
  • What happens if the string is fetched in some business logic?
func debugOutPrint(_ condition: Bool, _ message: String){ if condition { print("cjl_debug: \(message)") } } func doSomething() -> String{ print("doSomething") return "Network Error Occured" } <! DoSomething cjl_debug: Network Error Occured <! -- if false--> debugOutPrint(false, doSomething()Copy the code

It turns out that the current method is executed regardless of whether true or false is passed in, which is a waste of resources if the method is a very time-consuming operation. So to avoid this, you need to change the current parameter to a closure

  • “Change”Will:The message argument is changed to a closure, all you need to pass in is a function
Func debugOutPrint(_ condition: Bool, _ message:) func debugOutPrint(_ condition: Bool, _ message: () -> String){ if condition { print("cjl_debug: \(message())") } } func doSomething() -> String{ print("doSomething") return "Network Error Occured" } debugOutPrint(true, doSomething)Copy the code

The result is as follows

  • If you pass in one at this pointstringAnd how to deal with it?

You can declare the current closure as an automatic closure with @Autoclosure, which takes no arguments and returns the value of the current internal expression. So when you pass in a String, you’re essentially putting the String inside a closure expression that returns when called

Func debugOutPrint(_ condition: Bool, _ message:) func debugOutPrint(_ condition: Bool, _ message:) @autoclosure() -> String){ if condition { print("cjl_debug: \(message())") } } func doSomething() -> String{ print("doSomething") return "Network Error Occured" } <! -- Use 1: pass function --> debugOutPrint(true, doSomething()) <! DebugOutPrint (true, "Application Error Occured") <! --> doSomething cjL_debug: Network Error Occured CJL_debug: Application Error OccuredCopy the code

Automatic closures are equivalent to

DebugOutPrint (true, "Application Error Occured") {return "Network Error Occured"}Copy the code

conclusion

  • Escaping closure: A function that accepts a closure as an argument. Escaping closure may not be called until the function returns, i.e. the closure escapes the scope of the function. For example, on a network request, @escaping is required before the parameter to clarify that escaping is allowed.

    • Typically used for callbacks to asynchronous functions, such as network requests

    • If tagged for @escaping, self must be explicitly referenced in the closure

  • Non-escape closure: A function that takes a closure as an argument that is called before the function terminates, i.e. the closure is called before the function’s scope ends

  • Why is @escaping and @nonescaping distinguished?

    • 1. For memory management purposes, a closure will strongly reference all objects it captures, so that the closure will hold the current object, leading to circular references

    • 2. Non-escape closures do not generate circular references, they are used within the scope of the function, and the compiler can guarantee that the closure will release any objects it captures at the end of the function

    • 3. Using non-escape closures enables the compiler to apply more powerful performance optimizations, for example, eliminating retain and release calls when the life cycle of a closure is known

    • Memory for its context can be stored on the stack rather than on the heap

  • Bottom line: It is good for memory optimization to use non-escape closures in development if not specifically needed, so Apple has split closures into two categories and uses escape closures for special cases