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:

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.