1

I have an issue with strong references in one of my view controllers which is causing a memory leak. First, my setup:

2 view controllers (v1 and v2). v1 segues to v2, and v2 has a close button that pops itself back to v1. v2 contains code that tries to reconnect infinitely until a connection is made. (video streaming using red5pro). Here is the code:

func reconnect(){
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(5.0 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) { () -> Void in
            self.connectToStream()
        }
}

The continuous reconnecting is desirable in my situation, but when the user exits v2, I want the reconnecting to stop. But currently, the reconnect goes on infinitely, even when the user has left v2.

I have come to know that this is because v2 has strong references and continues to live even after the user exits from it. So this is causing the code that is infinitely calling the reconnect() method to continue running. I'm going to try to clean up v2 to convert everything to weak references, but I am also considering some alternatives, and I had a couple questions regarding it:

  1. Is there a way to kill the reconnecting on viewDidDisappear or something, so even if my view controller doesn't get destroyed, at least my reconnecting process stops?

  2. After exiting from v2 back to v1, if the user again segues to v2, is it possible to assign the same instance of v2 rather than creating a new instance of it each time?

Prabhu
  • 12,995
  • 33
  • 127
  • 210
  • "is it possible to assign the same instance of v1 rather than creating a new instance of it each time?" ... I assume you mean v2, not v1? – Rob Dec 13 '16 at 18:58
  • Assuming you really were talking about v2, not v1: Generally when you pop off a view controller, you free up the resources to it and you recreate it when you need to re-present it. There are ways to keep it around, but it's kludgy and smells of premature optimization and/or some deeper design flaw. View controllers are, themselves, relatively inexpensive to create. Why do you feel compelled to keep it around even though you've dismissed it? – Rob Dec 13 '16 at 19:03
  • @Rob yes, my bad, updated the question. – Prabhu Dec 13 '16 at 19:06
  • @Rob Actually I don't want to keep it around. But due to my strong references, it's not getting destroyed. While I am going to try to remove the strong references, if I am not able to for some reason, I was wondering about a backup plan if the user goes back from v1 to v2, so that I don't end up with multiple alive v2s. – Prabhu Dec 13 '16 at 19:07
  • 1
    You should just solve the problem of what is keeping v2 around, rather than contorting yourself to figure out how to reuse it. Xcode 8 has the "debug memory graph" and that will show you precisely what is maintaining a strong reference to v2. It's likely either a strong reference cycle of some form, or perhaps you have some circular storyboard references. (E.g. are you definitely popping from v2 back to v1, or do you have a segue from v2 to v1?) But the memory graph should make it easy to find the problem. – Rob Dec 13 '16 at 19:44
  • For example of "debug memory graph", see http://stackoverflow.com/a/30993476/1271826. – Rob Dec 13 '16 at 20:00

1 Answers1

3

The dispatch_after cannot be canceled, but there are a couple of options:

  1. Use weak references, which will allow self to be deallocated:

    func reconnect() {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(5.0 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) { [weak self] in
            self?.connectToStream()
        }
    }
    

    This admittedly keeps the timer going, but prevents it from retaining the view controller, so the view controller is released and connectToStream will not be called.

  2. Use NSTimer and cancel it when the view disappears:

    weak var timer: NSTimer?
    
    func reconnect() {
        timer?.invalidate()
        timer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: #selector(handleTimer(_:)), userInfo: nil, repeats: false)
    }
    
    func handleTimer(timer: NSTimer) {
        self.connectToStream()
    }
    
    override func viewDidDisappear() {
        super.viewDidDisappear()
        timer?.invalidate()
    }
    

    Note, because this selector-based NSTimer keeps a strong reference to its target, you cannot cancel in deinit (because there's a strong reference cycle). So you have to find some other appropriate event to resolve this (e.g. viewDidDisappear).

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Will give this a shot. If I'm able to resolve my issue without solving the view control reference cycle problem, is it ok to leave behind view controllers hanging? Or is the goal to always dispose off view controllers? – Prabhu Dec 13 '16 at 18:56