-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Open
Description
Summary
GutenbergCoverUploadProcessor has a retain cycle that causes it to never be deallocated after a cover block upload.
Root Cause
The lazy var coverBlockProcessor creates a GutenbergBlockProcessor with a replacer closure that strongly captures self:
// GutenbergCoverUploadProcessor.swift
lazy var coverBlockProcessor = GutenbergBlockProcessor(for: CoverBlockKeys.name, replacer: { coverBlock in
guard let mediaID = coverBlock.attributes[CoverBlockKeys.id] as? Int,
mediaID == self.mediaUploadID else { // strong capture of self
return nil
}
...
let innerProcessor = self.isVideo(attributes) ? self.videoUploadProcessor() : self.imgUploadProcessor()
...
})This creates a cycle:
GutenbergCoverUploadProcessor
→ lazy var coverBlockProcessor (strong)
→ GutenbergBlockProcessor.replacer closure (strong)
→ self (GutenbergCoverUploadProcessor) ← cycle
Detection
Detected using XCTestLeaks — a tool that runs leaks(1) after each XCTest case via a dylib shim.
Results from GutenbergCoverUploadProcessorTests before fix:
leaks=1 testCoverBlockProcessor
leaks=2 testCoverBlockProcessorWithOtherAttributes
leaks=3 testDeepNestedCoverBlockProcessor
leaks=4 testImageCoverInVideoCoverBlockProcessor
leaks=5 testMultipleCoverBlocksProcessor
leaks=5 testNestedCoverBlockProcessor
leaks=7 testUpdateOuterCoverBlockProcessor
leaks=7 testVideoCoverBlockProcessor
leaks=7 testVideoCoverInImageCoverBlockProcessor
After fix: all tests show leaks=0.
Fix
Use [weak self] in the replacer closure:
lazy var coverBlockProcessor = GutenbergBlockProcessor(for: CoverBlockKeys.name, replacer: { [weak self] coverBlock in
guard let self,
let mediaID = coverBlock.attributes[CoverBlockKeys.id] as? Int,
mediaID == self.mediaUploadID else {
return nil
}
...
})Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels