1

I have discovered that my UIViewcontroller is not calling deinit() under the following scenario. I am using this code extension to make my life easier by adding tap gesture recognizers.

https://gist.github.com/saoudrizwan/548aa90be174320fbaa6b3e71f01f6ae

I've used this code in one of my VCs, which I've stripped down to the barest minimum amount of code:

and in viewDidLoad() I did this:

// When the user taps on a label, have its related textbox automatically get the caret so they can type
// Add tapping so when you tap on a label it makes the corresponding textbox first responder
lblSubject.addTapGestureRecognizer {
 self.txtSubject.becomeFirstResponder()
}

It appears that the line:

self.txtSubject.becomeFirstResponder()

Is the problem - when I leave this line above in that closure, deinit() does not call in my VC. When I take the above line out or replace it with something like print("hello world") deinit() properly calls. txtSubject is @IBOutlet weak var txtSubject: UITextField!

I am not entirely sure what to do here. I read that when you trigger becomeFirstResponder() it's important you call resignFirstResponder(), but even if I don't tap the label (so as to not give becomeFirstResponder() a chance to even call) I still cannot hit deinit()

Any ideas where I can look further?

Thanks so much.

fatihyildizhan
  • 8,614
  • 7
  • 64
  • 88
NullHypothesis
  • 4,286
  • 6
  • 37
  • 79

2 Answers2

3

Change

self.txtSubject.becomeFirstResponder()

To

[unowned self] in self.txtSubject.becomeFirstResponder()

unowned is often feared as dangerous, but there is no danger here. If self ceases to exist, there will be nothing to tap and the code will never run.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • using [txtSubject] ended up not hitting deinit() and increasing memory. Using [unowned self] worked correctly and hit deinit()! thank you! – NullHypothesis Apr 18 '21 at 01:45
  • for every closure should I do unowned self in? For example: if (self.userProfile != nil){ if let cachedProfileImage = (UIApplication.shared.delegate as! AppDelegate).imageCache.image(withIdentifier: self.userProfile!.id.description) { self.imgAvatar.image = cachedProfileImage } else { UserService.getProfilePicture { response in self.imgAvatar.contentMode = .scaleAspectFill //set stuff here, work with response to set image.. } – NullHypothesis Apr 18 '21 at 01:52
  • None of those are closures. – matt Apr 18 '21 at 02:19
2

This is a classic retain loop. The self. inside of the closure is there to remind you to think about this. I assume that self is retaining lblSubject, and (via a OBJC_ASSOCIATION_RETAIN associated key), lblSubject is retaining self because it's captured by this closure.

You don't really need self here, however. You just need txtSubject. So you can just capture that:

lblSubject.addTapGestureRecognizer { [txtSubject] in
    txtSubject.becomeFirstResponder()
}

Alternately, you can fall back to the giant weak self hammer (though this tends to be greatly over-used):

lblSubject.addTapGestureRecognizer { [weak self] in
    self?.txtSubject.becomeFirstResponder()
}

The best way to explore this kind of bug is with Xcode's Memory Graph.

It is also a good idea to review the Swift docs on Automatic Reference Counting.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • 2
    I didn't know you could do `[txtSubject] in` like that... have been using the weak self hammer for everything... – aheze Apr 17 '21 at 15:05
  • 1
    @aheze Be warned, this trick makes a copy. So it works this way only for classes. With structs the effect is different (but also useful): it captures the value now rather than at the time the closure runs. – matt Apr 17 '21 at 21:36
  • @matt or aheze would you say it's better to use [weak self], [unowned self], or [txtSubject] or is it equivalent in my case – NullHypothesis Apr 18 '21 at 01:40