Closures capture a reference to a variable rather than a copy of the current variable

In Swift: Variables are divided into value types and reference types. If it is a reference type, the object’s reference is captured, that is, a copy of the object’s reference is made in the closure, and the object’s reference count is increased by one. In the case of a value type, the pointer to the value type is captured. Changing the value type in the closure also changes the value of the external variable.

func delay(_ duration: Int, closure: @escaping (a) -> Void) {
    let times = DispatchTime.now() + .seconds(duration)
    DispatchQueue.main.asyncAfter(deadline: times) {
        print("Start executing closures")
        closure()
    }
}

func captureValues(a) {
    var number = 1

    delay(1) {
        print(number)
    }

    number = 2
}

captureValues()
Copy the code

If you follow the previous line of thought, you might conclude: Output 1. Why? Because the closure directly captures a copy of the value itself, which is not the case in Swift, which captures a reference to a variable, not a copy of the value of the variable, the closure captures a reference to the number variable. When the closure executes, the pointer to the value type number is already 2, so the output here is

Start executing the closure2
Copy the code

Change the value of a variable in a closure

After changing the value of a variable outside the closure, the value of the captured variable will change. Of course, if you change the value of the variable inside the closure, will the value of the variable outside change? The answer, of course, is yes. Changing the value of a variable in a closure also changes the actual value of the variable through a pointer, so it definitely changes

func changeValues(a) {
    var number = 1

    delay(1) {
        print(number)
        number += 1
    }

    delay(2) {
        print(number)
    }
}
Copy the code

The output value is:

Start executing the closure1Start executing the closure2
Copy the code

How does a closure capture the value of a variable instead of a reference?

So we certainly sometimes have a requirement that we just want to capture the value of the current variable, and we don’t want changes to the value of the variable elsewhere to affect the value captured by the closure before it executes. To do this, Swift provides capture lists that capture copies of variables, not Pointers to variables!

  func captureStatics(a) {
      var number = 1

      // At compile time, count copies the value of the variable
      delay(1) {[count = number] in
          print("count = \ [count)")
          print("number = \(number)")
      }

      number += 10
  }
Copy the code

The output is as follows:

Start executing the closurecount = 1
number = 11
Copy the code

Two keywords for the closure

When we talk about closures, we have to mention the keywords @escaping and @Autoclosure, which refer to escaping and automatic closures, respectively

@escaping

  • What is an escape closure? When a closure is passed as an argument to a function and is not executed until the function returns, the closure is called an escape closure
  • If the closure does an asynchronous operation inside the function body, the function will normally complete quickly and return, but the closure must escape in order to handle the asynchronous callback
  • Escape closures are heavily used in network requests to handle network callbacks
func delay(_ duration: Int, closure: @escaping (a) -> Void) {
    let times = DispatchTime.now() + .seconds(duration)
    DispatchQueue.main.asyncAfter(deadline: times) {
        print("Start executing closures")
        closure()
    }
    print("Method completed")}Copy the code

This method is a classic example. Closures passed in as arguments are executed late, so functions return values before closures execute, so the @escaping keyword needs to be added to the closure argument

Method completes execution and executes the closureCopy the code

@autoclosure

In fact, automatic closures, mostly heard and used, simplify parameter passing and delay execution time. So let’s write a simple method

func autoTest(_ closure: (a) -> Bool) {
    if closure() {

    } else{}}Copy the code

This is a method that takes a closure as an argument, and the closure is not executed after the function returns, but as a condition in the method body. How do we call this method?

  autoTest { () -> Bool in
      return "n"= ="b"
  }
Copy the code

Of course, since the closure defaults to the last expression as the return value, this can be simplified to:

autoTest { "n"= ="b" }
Copy the code

So could it be more concise? The answer is yes, use the @autoclosure keyword in the closure

func autoTest(_ closure: @autoclosure (a) -> Bool) {
    if closure() {

    } else{}}Copy the code
autoTest("n"= ="b")
Copy the code

@autoClosure @autoclosure @autoclosure @autoclosure @autoclosure @autoclosure @autoclosure @autoclosure @autoclosure This works in theory, but if you use an expression directly, when you call a method, the expression evaluates and then passes the value as an argument to the method. If it is an @autoclosure, it will only be executed when it needs to be executed, and will not be computed in the first place, similar to lazy loading

  • The main difference between @Autoclosure and normal expressions is that normal expressions are executed immediately after passing arguments, and the result of the execution is passed to the function as arguments
  • With the @autoclosure tag argument, we pass in an expression that will not be executed immediately, but when it will be executed will depend on the function being called

Circular references to closures

Object -> closures -> objects form circular references and cannot release each other, forming circular references! So here’s the question:

UIView.animate(withDuration: TimeInterval) {

}

DispatchQueue.main.async {

}
Copy the code

Does calling the method with self in the above two closures cause a circular reference? Really? Of course not. Self must hold a closure in order for a circular reference to be possible in the first place, but self does not hold a closure. More on this later, let’s take a look at the two keywords in the closure, Weak and Owned Apple suggest using unowned if you can be sure that self will not be freed when accessed, and Weak if there is a possibility that self will be freed

[weak self]

Let’s look at a simple example

class Person {
    var name: String
    lazy var printName: () -> () = {
        print("\ [self.name)")}init(name: String) {
        self.name = name
    }
    
    deinit {
        print("\(name)Be destroyed")}}func test(a) {
  let person = Person.init(name: "Xiao Ming")
  person.printName()
}

text()
Copy the code

The output is:

Xiao MingCopy the code

Why is that? Anyone who knows anything about circular references knows that the main reason for this is that self holds the closure, and the closure holds self, so the circular reference is created and the Ming object is not freed. So you can use weak at this point, and the Person object can be freed normally, except that if it’s asynchronous, when the Person object is freed, the statement in the closure won’t be executed because self is already nil

class Person {
    var name: String
    lazy var printName: () -> Void= {[weak self] in
        print("\ [self? .name)")}init(name: String) {
        self.name = name
    }
    
    deinit {
        print("\(name)Be destroyed")}func delay(_ duration: Int, closure: @escaping (a) -> Void) {
        let times = DispatchTime.now() + .seconds(duration)
        DispatchQueue.main.asyncAfter(deadline: times) {
            print("Start executing closures")
            closure()
        }
    }
}

let person = Person.init(name: "Xiao Ming")
person.delay(2, closure: person.printName)
Copy the code

The results are as follows:

Ming gets destroyed and starts executing closure nilCopy the code

This is the advantage and disadvantage of using weak. It does avoid bad references, but it does not guarantee that all the statements in the closure will be executed, so consider strongSelf in OC, which means that all or none of the statements in the closure will be executed:

lazy var printName: () -> Void= {[weak self] in
    guard let strongSelf = self else {
        return
    }
    print(strongSelf.name)
}
Copy the code

This is the one that we use most in the real world, either do it all or do it none; Is there a way to avoid circular references while still keeping the code intact? The answer is yes, as mentioned in a blog post by Tang Qiao, there are two ways to make a block avoid circular references:

  1. Weak, unowne
  2. Ex post facto, which means manually disconnecting a block after it is passed in
  lazy var printName: () -> Void = {
       print(self.name)
      self.printName = {}
  }

Copy the code

The following output is displayed:

-- -- -- -- -- -- -- -- -- -- -- -- begin to execute closure - xiao Ming -- -- -- -- -- -- -- end closures execution -- -- -- -- -- -- -- -- -- xiao Ming object is destroyedCopy the code

In effect, I break the closure’s hold on self after execution. The advantage of this approach is that I don’t create a circular reference, and I can ensure that the code in the closure executes completely, but there is a risk that I can still create a circular reference if I forget to actively disconnect.

[unowned self]

This is pretty straightforward: use the unowned keyword if the life of self is the same as, or longer than, the closure. In practice, follow Apple’s recommendations:

Use self if you can be sure that self will not be released when accessedunowned, if self has a possibility of being releasedweak


True circular references

Why do I mention circular references? Of course, I mainly talk about closures, because many people use weak crazily in the process of using weak, but do not know exactly what circumstances will cause circular references! The simple fact is that a circular reference only happens when self holds a closure, that is, if the closure is a property of self!

class Person {
    var name: String
    lazy var printName: () -> Void = {
         print(self.name)
        self.printName = {}
    }

    init(name: String) {
        self.name = name
    }

    deinit {
        print("\(name)Object destroyed")}func delay2(_ duration: Int) {
        let times = DispatchTime.now() + .seconds(duration)
        DispatchQueue.main.asyncAfter(deadline: times) {
            print("------- Start executing closure --------")
            print(self.name)
            print("------- end execution closure ---------")}}}func test2(a) {
    let person = Person.init(name: "Xiao Ming")

    person.delay2(2)
}

test2()
Copy the code

Can you guess, will the object be destroyed?

-- -- -- -- -- -- -- -- -- -- -- -- begin to execute closure - xiao Ming -- -- -- -- -- -- -- end closures execution -- -- -- -- -- -- -- -- -- xiao Ming object is destroyedCopy the code

Somebody asked? No, I’m using self in the closure, why doesn’t that create a circular reference? Since a circular reference is a loop with at least two holds, one self -> closure and one closure -> self, obviously the latter, including web requests we use most of the time, as long as self doesn’t hold a callback closure, it doesn’t actually cause a circular reference!

The question is, why do many people use weak self in network requests? In my opinion, this is necessary because many times you are not sure if the class you are requesting holds the closure you passed in, so use weak or unowned instead

Okay, so if you look at this, there’s another question, why hasn’t the closure been released since self doesn’t hold it? This brings us to another point, which is that in Swift closures and classes are reference types, and when you pass closures as parameters into a network request, they actually end up being held by the system, for example with Alamofilre request data, calling a request method ends up in the following area

(queue ?? DispatchQueue.main).async { completionHandler(dataResponse) }
Copy the code

UIView animations, DispatchQueue and so on that we use are actually held by the system are not released, this should be understood, of course, this is just my inference, if anyone knows more details, or I understand wrong, please let me know, thank you very much ~

And then my little conclusion is that if you use DispatchQueue to catch not a reference to the closure, but a copy of the closure. (When the system captures a closure as an object, does it capture a copy or a reference?)

var test = {
    print("first")}UIView.animate(withDuration: 0.2, delay: 0.5, options: UIViewAnimationOptions.curveLinear, animations: {
    test()
}, completion: nil)

test = {
    print("second")}Copy the code

Output:

first
Copy the code

It is obvious that the system is capturing a copy of the closure, not a reference to it!!

What about closures as arguments to methods? Does the method also capture a copy of the closure? Let’s test it out:

class Person {
    var name: String

    init(name: String) {
        self.name = name
    }
    
    func test(cloure: (a) -> Void) {
        cloure()
    }
}


var cloure = {
    print("Little brother")}DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.0) {
    person.test(cloure: cloure)
}

cloure = {
    print("Eldest brother")}Copy the code

The output

The eldest brotherCopy the code

The method passes a pointer to a pointer, but the output is a pointer to a pointer. As you probably know, the method gets a reference to a closure!

conclusion

I hope I can give you some reference, I think in the process of learning, or should think a little more, do not dabble. Progress together!