Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Weak self in closures and consequences example

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

  1. 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?

  2. 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 ....

  3. 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

like image 690
user172902 Avatar asked Aug 21 '16 03:08

user172902


People also ask

What is weak self in closure?

In Swift, [weak self] prevents closures from causing memory leaks in your application. This is because when you use [weak self], you tell the compiler to create a weak reference to self. In other words, the ARC can release self from memory when necessary.

Do we need to use weak self or unowned self in this closure?

“Use a weak reference whenever it is valid for that reference to become nil at some point during its lifetime. Conversely, use an unowned reference when you know that the reference will never be nil once it has been set during initialisation.” Save this answer.

What is strong what is weak why we want to use unowned and weak self?

In Swift, we need to use weak self and unowned self to give ARC the required information between relationships in our code. Without using weak or unowned you're basically telling ARC that a certain “strong reference” is needed and you're preventing the reference count from going to zero.

Why do you generally create a weak reference when using self in a block?

because block are mean to be executed at a later time, so it need to keep strong reference to all the object it access. Block can be executed MANY TIMES, so IT WON'T RELEASE self AFTER this ran. When you nil out the block, it will be dealloc, hence it will decrease the reference count to all the objects it access.


1 Answers

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.

  1. 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.

  2. In case 2, you theoretically could use [weak self] in the animation 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 use weak here suggests a misunderstanding of the memory semantics involved.

  3. 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 time self is dismissed, and you don't want to have checkUserLoggedIn 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:

      weak var weakSelf = self 
      

      That is a bit unswifty. You would use the [weak self] pattern at the start of the checkUserLoggedIn closure. If you have an example where you're tempted to use weak 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.

like image 158
Rob Avatar answered Sep 29 '22 00:09

Rob