Swift Task Continuation泄漏问题排查及代码优化求助
问题根源分析
1. Continuation未被正确Resume(核心触发原因)
saveAndClose()中使用的withCheckedContinuation要求所有代码路径必须调用resume,但当前代码存在以下场景会导致continuation永久挂起:
- 若
saveDocument或closeDocument底层逻辑异常(如文件锁死、资源占用),其completion handler未触发,onCompletion就不会执行,continuation永远无法resume,触发泄漏警告。 - 当文档未成功打开(
isOpen == false)时,仍调用saveAndClose(),此时文档处于未初始化状态,后续操作可能卡住,无法触发onCompletion。
2. 串行循环的阻塞风险
当前循环为串行执行,某一项的saveAndClose()阻塞会导致整个流程停滞,后续所有项无法执行。
修复方案
方案1:确保Continuation在所有场景下被Resume
修改saveAndClose(),添加超时兜底和弱引用保护,避免continuation泄漏:
func saveAndClose() async -> Bool { return await withCheckedContinuation { [weak self] continuation in guard let self = self else { continuation.resume(returning: false) return } var didResume = false // 超时兜底:10秒后强制resume,避免永久挂起 DispatchQueue.global().asyncAfter(deadline: .now() + 10) { if !didResume { didResume = true continuation.resume(returning: false) } } self.saveAndCloseWithCompletionHandler { isSuccess in if !didResume { didResume = true continuation.resume(returning: isSuccess) } } } }
同时检查saveDocument和closeDocument的所有分支,确保completionHandler一定会被调用(比如super.save/super.close的底层实现是否存在异常不回调的情况,必要时添加兜底)。
方案2:优化循环中的文档处理逻辑
仅在文档成功打开时执行saveAndClose(),避免对未初始化文档执行无效操作:
private func processTags(reqItems: [FTShelfItemProtocol], selectedTags: [String]) async throws -> FTShelfTagsResult { let items: [FTDocumentItemProtocol] = reqItems.filter({ ($0.URL.downloadStatus() == .downloaded) }).compactMap({ $0 as? FTDocumentItemProtocol }) var totalTagItems: [FTShelfTagsItem] = [] for item in items where item.documentUUID != nil { guard let docUUID = item.documentUUID else { continue } let destinationURL = FTDocumentCache.shared.cachedLocation(for: docUUID) do { let document = await FTNoteshelfDocument(fileURL: destinationURL) let isOpen = try await document.openDocument(purpose: .read) var needClose = false if isOpen { needClose = true let tags = await document.documentTags() let considerForResult = selectedTags.allSatisfy(tags.contains(_:)) if considerForResult && !tags.isEmpty { var tagsBook = FTShelfTagsItem(shelfItem: item, type: .book) tagsBook.tags = tags totalTagItems.append(tagsBook) } } let tagsPage = await document.fetchSearchTagsPages(shelfItem: item, selectedTags: selectedTags) totalTagItems.append(contentsOf: tagsPage) // 仅在文档成功打开时执行关闭操作 if needClose { _ = await document.saveAndClose() } } catch { cacheLog(.error, error, destinationURL.lastPathComponent) } } cacheLog(.success, totalTagItems.count) return FTShelfTagsResult(tagsItems: totalTagItems) }
方案3:并行处理循环任务(可选,提升性能)
如果业务允许,将串行循环改为并行处理,避免单个任务阻塞整个流程:
private func processTags(reqItems: [FTShelfItemProtocol], selectedTags: [String]) async throws -> FTShelfTagsResult { let items: [FTDocumentItemProtocol] = reqItems.filter({ ($0.URL.downloadStatus() == .downloaded) }).compactMap({ $0 as? FTDocumentItemProtocol }) // 并行处理每个文档 let tagItemsArrays = await withTaskGroup(of: [FTShelfTagsItem].self) { group in for item in items where item.documentUUID != nil { guard let docUUID = item.documentUUID else { continue } let destinationURL = FTDocumentCache.shared.cachedLocation(for: docUUID) group.addTask { var tagItems: [FTShelfTagsItem] = [] do { let document = await FTNoteshelfDocument(fileURL: destinationURL) let isOpen = try await document.openDocument(purpose: .read) var needClose = false if isOpen { needClose = true let tags = await document.documentTags() let considerForResult = selectedTags.allSatisfy(tags.contains(_:)) if considerForResult && !tags.isEmpty { var tagsBook = FTShelfTagsItem(shelfItem: item, type: .book) tagsBook.tags = tags tagItems.append(tagsBook) } } let tagsPage = await document.fetchSearchTagsPages(shelfItem: item, selectedTags: selectedTags) tagItems.append(contentsOf: tagsPage) if needClose { _ = await document.saveAndClose() } } catch { cacheLog(.error, error, destinationURL.lastPathComponent) } return tagItems } } // 收集所有并行任务的结果 var allTagItems: [FTShelfTagsItem] = [] for await result in group { allTagItems.append(contentsOf: result) } return allTagItems } cacheLog(.success, tagItemsArrays.count) return FTShelfTagsResult(tagsItems: tagItemsArrays) }
额外注意事项
- 检查
FTNoteshelfDocument的生命周期,使用弱引用避免循环引用导致实例无法释放; - 为文件操作添加详细日志,便于定位具体是哪个文档导致的阻塞;
- 在
saveAndClose中提前判断openPurpose,只读模式下跳过保存步骤,减少无效操作。
内容的提问来源于stack exchange,提问作者Narayana MV
相关产品推荐
相关产品推荐

