WKWebView causes my view controller to leak
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
When a view controller with WKWebView never deallocates after dismissal, the issue is almost always a retain cycle. The most frequent cycle involves script message handlers, closures, or observers that hold strong references. Fixing this requires explicit lifecycle cleanup, not only hiding the screen.
Common Retain Cycle Topology
A typical leak chain is:
- View controller strongly owns
WKWebView. - Web view configuration owns
WKUserContentController. - User content controller strongly owns script message handler.
- Handler strongly references view controller.
If the controller registers itself directly as handler and never removes it, deallocation will not happen.
Use a Weak Script Handler Proxy
A weak forwarding proxy breaks the strongest cycle path.
If you prefer direct handler registration, you must remove handler explicitly on teardown.
Teardown Must Happen Before Deinit
Waiting for deinit is too late if a cycle already exists. Add cleanup in lifecycle methods that run on dismissal.
This proactively breaks references even when dismissal path is not linear.
Watch Closure Captures and Timers
Leaks are not only about message handlers. Closures, timers, and async tasks often capture controller strongly.
Use weak captures for callbacks owned longer than the controller lifecycle.
For Timer, invalidate on teardown and avoid strong self capture in timer blocks.
Debugging Workflow in Xcode
A practical leak-debug flow:
- Add a
deinitprint in controller. - Present and dismiss controller repeatedly.
- Check whether
deinitlogs each cycle. - Use Memory Graph to inspect retain path if
deinitis missing.
Memory Graph usually points directly to the retaining owner, such as a script handler list or closure storage.
Architecture Patterns That Reduce Leaks
For web-heavy apps, isolate bridge setup and cleanup into a reusable component. This avoids copy-paste lifecycle bugs.
Also avoid singleton web view objects carrying controller-specific bridge logic. Shared web views can work, but only when message routing and delegate ownership are deliberately separated.
A simple team practice is adding a leak regression QA step for web screens: open, interact with bridge, dismiss, repeat. This catches regressions before release.
Common Pitfalls
- Registering controller directly as script handler and never removing it.
- Assuming delegate nullification alone breaks all web view retention paths.
- Using strong closure captures in async callbacks tied to web events.
- Relying on
deinitcleanup when cycle already prevents deinit. - Reusing long-lived web infrastructure with screen-specific ownership state.
Summary
- Most
WKWebViewleaks are retain-cycle problems, not random framework defects. - Script message handlers are the highest-risk ownership point.
- Use weak proxy handlers or explicit handler removal during dismissal.
- Clean delegates, observers, and async captures in one predictable teardown path.
- Verify fixes with deinit logs and memory graph inspection.

