I have done abit of research on stackoverflow and apple's documentation about ARC and Weak/Unowned self (Shall we always use [unowned self] inside closure in Swift). I get the basic idea about strong reference cycle and how it is not good as they cause memory leaks. However, I am trying to get my head around when to use Weak/Unowned self in closures. Rather then going into the "theory", I think it would really really help if someone could kindly explain them in terms of the bottom three cases that I have. My questions is
-
Is it OK to put weak self in all of them (I think for case two there is no need because I saw somewhere that UIView is not associated with self?. However, what if I put weak self there, is there anything that can cause me headache?
-
Say if the answer is No, you cant put weak self everywhere in all three cases, what would happen if I do (example response would be much appreciated…For example, the program will crash when this VC ….
-
This is how I am planning to use weakSelf
Outside the closure, I put weak var weakSelf = self
Then replace all self in closure with weakSelf?
Is that OK to do?Case 1: FIRAuth.auth()?.signInWithCredential(credential, completion: { (user: FIRUser?, error: NSError?) in self.activityIndicatorEnd() self.performSegueWithIdentifier(SEGUE_DISCOVER_VC, sender: self) }) Case 2: UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration: 0.1, animations: { self.messageLbl.alpha = 0.5 }) Case 3: //checkUserLoggedIn sends a request to firebase and waits for a response to see if the user is still authorised checkUserLoggedIn { (success) in if success == false { // We should go back to login VC automatically } else { self.discoverTableView.delegate = self self.discoverTableView.dataSource = self // Create dropdown menu let menuView = BTNavigationDropdownMenu(navigationController: self.navigationController, title: self.dropDownItems.first!, items: self.dropDownItems) menuView.didSelectItemAtIndexHandler = {[weak self] (indexPath: Int) -> () in if indexPath == 0 { self?.mode = .Closest self?.sortByDistance() } else if indexPath == 1 { self?.mode = .Popular self?.sortByPopularity() } else if indexPath == 2 { self?.mode = .MyPosts self?.loadMyPosts() } else { print("Shouldnt get here saoihasiof") } } // Xib let nib = UINib(nibName: "TableSectionHeader", bundle: nil) self.xibRef = nib.instantiateWithOwner(self, options: nil)[0] as? TableSectionHeader self.discoverTableView.registerNib(nib, forHeaderFooterViewReuseIdentifier: "TableSectionHeader") // Set location Manager data self.locationManager.delegate = self self.locationManager.desiredAccuracy = kCLLocationAccuracyBest // Check location service status if self.locationAuthStatus == CLAuthorizationStatus.AuthorizedWhenInUse { // Already authorised self.displayMessage.hidden = false } else if self.locationAuthStatus == CLAuthorizationStatus.NotDetermined { // Have not asked for location service before let storyboard = UIStoryboard(name: "Main", bundle: nil) let vc = storyboard.instantiateViewControllerWithIdentifier("LocationVC") as! LocationVC vc.locationVCDelegate = self self.presentViewController(vc, animated: true, completion: nil) } else { let alertController = UIAlertController(title: "Enable Location", message: "location is required to load nearby posts", preferredStyle: .Alert) let cancelAction = UIAlertAction(title: "Cancel", style: .Default, handler: nil) let settingsAction = UIAlertAction(title: "Settings", style: .Default, handler: { (action: UIAlertAction) in let settingsUrl = NSURL(string: UIApplicationOpenSettingsURLString) if let url = settingsUrl { UIApplication.sharedApplication().openURL(url) } }) alertController.addAction(settingsAction) alertController.addAction(cancelAction) self.presentViewController(alertController, animated: true, completion: nil) self.displayMessage.hidden = false self.displayMessage.text = "Could not determine your location to find nearby posts. Please enable location Service from settings" } // Styling self.refreshBtn.tintColor = COLOR_NAVIGATION_BUTTONS self.discoverTableView.backgroundColor = COLOR_DISCOVERVC_TABLEVIEW_BACKGROUND // Allow navigation bar to hide when scrolling down self.hidingNavBarManager = HidingNavigationBarManager(viewController: self, scrollView: self.discoverTableView) // Allow location to start updating as soon as we have permission self.locationManager.startUpdatingLocation() } }
–Update–
Most of my code looks like case 3 where everything is wrapped inside a closure that either check if there is internet connectivity before any of the action is taken place. So I might have weak self everywhere??
–Update 2–
Case 4:
// The haveInternetConnectivity function checks to see if we can reach google within 20 seconds and return true if we can
haveInternetConnectivity { (success) in
if success == false {
self.dismissViewControllerAnimated()
} else {
self.label.text = "You are logged in"
self.performSegueWithIdentifier("GoToNextVC")
}
}
Question about case 4.
Am I correct to say that even though this closure does not have weak/unowned self, it will never create strong reference (and memory leak) because even if the VC is dismissed before the completion block is executed, Xcode will try to run the code inside the completion block when we have confirmed internet status and will just do nothing (No crash) because self doesn't exist anymore. And once the code reached the last line inside the closure, the strong reference to self would be destroyed hence deallocate the VC?
So putting [weak Self] in that case would just mean that xcode would ignore those lines (as oppose to try and run it and nothing happens) which would mean a better practice but no issues on my hand either way
Best Answer
The question should not be "can I use weak reference," but rather "should I use weak reference." You use weak references to avoid strong reference cycles or to keep a closure from hanging on to something after it may have been disposed. But don't just add weak references because you can.
In case 1, you probably do want to use
[weak self]
. Why? Because if the view controller was dismissed while the authorization was underway, do you really want to keep a reference to a view controller that was dismissed? Probably not in this case.In case 2, you theoretically could use
[weak self]
in theanimation
block, but why would you? There's no reason to. The weak reference is something you do with completion handlers and/or closure variables, but for an animation block it offers no utility, so I wouldn't do it there. To useweak
here suggests a misunderstanding of the memory semantics involved.In case 3, you have two separate issues.
In the
didSelectItemAtIndexHandler
, that probably should use[unowned self]
because the object's own closure is referring to itself.It may be a moot issue, as I don't see you actually using that
BTNavigationDropdownMenu
(perhaps that initializer is adding itself to the navigation controller, but that's not a well designed initializer if so, IMHO).But as a general concept, when an object has a handler closure that can only be called when the object is still around, but shouldn't, itself, cause the object to be retained, you'd use
[unowned self]
.In the broader
checkUserLoggedIn
closure, the question is whether that's a completion handler. If so, you probably should use[weak self]
, because this could be initiated and be running by the timeself
is dismissed, and you don't want to havecheckUserLoggedIn
keep a reference to a view controller that was dismissed. But you wouldn't want to use[unowned self]
because that would leave you with dangling pointers if it had been released by the time the closure runs.As an aside, you contemplate:
That is a bit unswifty. You would use the
[weak self]
pattern at the start of thecheckUserLoggedIn
closure. If you have an example where you're tempted to useweak var weakSelf = ...
, you should edit your question, including an example of where you want to use that pattern. But this is not one of those cases.