fix: Resolve iOS retain cycle in EditorViewController async flow#333
Merged
fix: Resolve iOS retain cycle in EditorViewController async flow#333
Conversation
EditorService.prepare() stored the progress callback permanently, creating a retain cycle when EditorViewController owned the service and the callback captured the view controller. Clear the callback on completion and use weak captures in the Task and closure.
jkmassel
approved these changes
Feb 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What?
Fixes a retain cycle that prevents
EditorViewControllerfrom being deallocated when using the async dependency-loading flow.Why?
Fix CMM-1282.
When opening and closing the editor multiple times without pre-loaded dependencies, each
EditorViewControllerinstance leaks —deinitis never called and Safari's Web Inspector shows lingering web views.The root cause is
EditorService.prepare()storing the progress callback in a property that is never cleared. SinceEditorViewControllerowns theEditorServiceand the callback captures the view controller, this creates a retain cycle:This only affects the async flow (no pre-loaded dependencies) because:
prepare()returns early before storing the callbackIn WordPress-iOS, the issue is reproducible by using the pull-to-refresh gesture on My Site to invalidate the editor dependencies cache, then opening the editor.
How?
EditorService.swift: Addeddefer { self.progressCallback = nil; self.progress = nil }inprepare()to clear the stored callback after completion, breaking the cycle from the service side.EditorViewController.swift: Added[weak self]captures in the Task closure and progress callback as defense-in-depth, ensuring the cycle cannot form even if the service retains the callback.Either fix alone resolves the issue; both together provide robustness.
Testing Instructions
deinittoEditorViewControllerand confirm it fires on each dismissal