itenyh
itenyh

Reputation: 1939

Add observer to runloop creates retain cycle

let observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, CFRunLoopActivity.BeforeWaiting.rawValue, false, 0, { (observer, activity) in

        self.doSomething()

    })

    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, UITrackingRunLoopMode)

I add an observer to main runloop in a UIViewController which means self is an instance of UIViewController. The code above create a retain cycle which lead to the controller will never be released.

I know that I can declare [weak self] or [unowned self] for the block to solve the problem. I want to ask what is exactly the retian cycle look like? I only konw that the block captures self with a strong reference.

Upvotes: 3

Views: 988

Answers (2)

Rob
Rob

Reputation: 438437

If you run this in “Debug Memory Graph” with “Malloc Stack” feature turned on (and “Malloc Scribble” to reduce false positives), you will see that your observer’s closure is likely keeping a strong reference to your view controller:

memory graph

And because we turned on “Malloc Stack” feature, you can click on the stack on the right and it will take you directly to the code where that strong reference was established. (Needless to say, when you are done debugging, turn off “Malloc Stack”.)

As a general rule, when you have a closure that references self, you will want to use [weak self] pattern:

let observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, CFRunLoopActivity.beforeWaiting.rawValue, false, 0) { [weak self] observer, activity in
    self?.doSomething()
}

That breaks the strong reference that the run loop’s observer would be maintaining to your view controller. Then, again, if your observer could still be present by the time the view controller is dismissed, you will likely want to remember to CFRunLoopRemoveObserver when the view controller is dismissed.

All of this having been said, if your observer is not repeating and you are not keeping a reference to it, generally this closure would be deallocated on its own and your strong reference to the view controller would have been automatically resolved. But the details here are less important than the general observation that the “debug memory graph” tool will help find the strong references, wherever they may be.

For more information, see WWDC 2016 Visual Debugging with Xcode, starting about 24 minutes into that video, will show you how to diagnose these sorts of issues.

Upvotes: 3

rob mayoff
rob mayoff

Reputation: 386038

There is no retain cycle in the code in your question.

  • The run loop retains the observer.
  • The observer retains the block.
  • The block retains self.

That is not a retain cycle.

However, if you store observer in a strong instance variable, then you have a retain cycle:

  • self retains the observer.
  • The observer retains the block.
  • The block retains self.

Upvotes: 1

Related Questions