如何避免子ViewController内存泄漏?两种场景下的正确处理方式
Hey there! Let's break down how to handle memory management for both your static (ViewController2 via Storyboard Container View) and dynamic (ViewController3 added via code) child view controllers to avoid those pesky memory leaks. Here's a step-by-step breakdown for each scenario:
First, let's clarify the built-in relationship here: When you use a Storyboard Container View, UIKit automatically sets up a parent-child relationship between your main ViewController1 and ViewController2. The system already holds a strong reference to ViewController2 in ViewController1's children array—so you don't need to add another strong reference yourself.
The Fix: Use a Weak Reference for ViewController2
If you need to keep a reference to ViewController2 in ViewController1 for later access, always use a weak variable to avoid creating a retain cycle. Also, if ViewController2 needs a reference back to ViewController1, make sure that reference is weak too.
Example code for ViewController1:
// Inside ViewController1 weak var viewController2: ViewController2? override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if let childVC = segue.destination as? ViewController2 { viewController2 = childVC // Pass a weak reference of ViewController1 to ViewController2 if needed childVC.parentVC = self // In ViewController2, declare this as `weak var parentVC: ViewController1?` } }
Why This Works:
- UIKit's
childrenarray already maintains a strong hold on ViewController2, so your weak reference is enough to access it when needed. - The weak back-reference from ViewController2 to ViewController1 ensures there's no circular strong reference that would prevent both VCs from being deallocated.
When adding a child VC programmatically, you're responsible for managing the full parent-child lifecycle and its references. Here's how to do it right:
Step 1: Use a Strong Reference (If Needed)
If you need to access ViewController3 after adding it, store it in a strong variable in ViewController1—but you must explicitly clean it up when it's no longer needed.
Step 2: Follow the Full Child VC Lifecycle
Always call the required parent-child methods to ensure UIKit properly manages the VC's lifecycle, and then break the strong reference when done.
Example code for ViewController1:
// Inside ViewController1 var viewController3: ViewController3? func addDynamicChildVC() { let childVC = ViewController3() // Establish parent-child relationship addChild(childVC) // Add the child's view to your hierarchy childVC.view.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 200) view.addSubview(childVC.view) // Finalize the relationship childVC.didMove(toParent: self) viewController3 = childVC // Again, pass a weak reference if the child needs to talk back childVC.parentVC = self // In ViewController3, this is `weak var parentVC: ViewController1?` } // Call this when you no longer need ViewController3 (e.g., on screen dismiss, or parent VC deinit) func removeDynamicChildVC() { guard let childVC = viewController3 else { return } // Start the removal lifecycle childVC.willMove(toParent: nil) // Remove the view from the hierarchy childVC.view.removeFromSuperview() // Finalize removal from parent childVC.removeFromParent() // Break the strong reference to allow deallocation viewController3 = nil } // Always clean up in deinit to catch cases where removeDynamicChildVC wasn't called deinit { removeDynamicChildVC() }
Key Notes:
- Skipping any of the parent-child lifecycle methods (
addChild,didMove(toParent:), etc.) can lead to unexpected memory issues, as UIKit won't properly manage the child VC's state. - Setting
viewController3 = nilis critical—it breaks the strong reference chain, allowing both the child and parent to be deallocated when they're no longer needed.
- Avoid Bidirectional Strong References: Never have both parent and child holding strong references to each other—this creates a retain cycle that will leak memory indefinitely.
- Use Xcode's Memory Graph Debugger: If you suspect a leak, use this tool to inspect the reference chain of your view controllers and spot any unexpected strong holds.
- Keep Child VCs Self-Contained: Minimize the need for parent-child references where possible—use delegation with weak delegates, or closure-based callbacks with
[weak self]capture lists to avoid unnecessary references.
内容的提问来源于stack exchange,提问作者Bruno Morgado

