输入字符过快时DispatchWorkItem未及时取消引发CFString释放错误求解
解决快速搜索时的
[CFString length]: message sent to deallocated instance崩溃问题 我帮你拆解下你遇到的问题,核心原因是后台任务取消不彻底+内存访问不安全,再加上快速输入时的任务堆积,最终导致了野指针访问崩溃。下面是具体的分析和解决方案:
问题根源拆解
- 未通过弱引用访问实例变量:在
anyTranslate里你直接访问test和searchText,如果当前ViewController已经被释放,这些访问会直接触发野指针错误(也就是你看到的[CFString length]崩溃,本质是访问了已释放内存)。 - 任务取消逻辑漏洞:
willSet里只是调用了ind?.cancel(),但旧的后台任务可能还在执行循环,而且你直接用新的DispatchWorkItem覆盖work[i],导致旧任务失去引用,无法追踪是否真正停止。 - 数据竞争风险:多个后台线程同时调用
addWord修改words数组,可能导致数组内部结构损坏,进而引发崩溃。 - 无搜索防抖:快速输入时频繁触发搜索,导致大量后台任务被创建和取消,不仅损耗性能,还容易出现任务执行混乱的情况。
分步解决方案
1. 修复内存访问安全问题
所有实例变量的访问必须通过weak self解包后进行,避免访问已释放的对象:
func anyTranslate(with words: Words, and lang: String) { guard let self = self else { return } // 先确保self存在 let array = Language.getLang(lang: lang, word: words) for value in array[0] ?? [""] { if self.test == true { print(true) return // 检测到任务取消,直接退出 } if value.hasPrefix(self.searchText) { self.addWord(with: words) return } } }
2. 完善任务取消与追踪逻辑
更新searchText的监听逻辑,确保旧任务被彻底取消,同时避免覆盖未完成的任务引用:
private var searchText = "" { willSet { // 取消所有正在运行的搜索任务 work.forEach { $0?.cancel() } // 清空work数组,释放旧任务引用 work = Array(repeating: nil, count: 5) test = true } didSet { test = false if searchText.count >= 2 { search(nil) } else { // 文本长度不足时,直接清空结果并更新UI DispatchQueue.main.async { self.words.removeAll() self.tableView.reloadData() self.noFound() self.tableView.isUserInteractionEnabled = true self.indicator.stopAnimating() } } } }
同时优化search函数,确保每个后台任务能及时响应取消,并且所有任务完成后再统一更新UI:
func search(_ completion: (() -> ())?) { update = true words.removeAll() DispatchQueue.main.async { self.tableView.isUserInteractionEnabled = false self.indicator.isHidden = false self.indicator.startAnimating() } let arr = [ViewController.splitArr.first, ViewController.splitArr.second, ViewController.splitArr.third, ViewController.splitArr.four, ViewController.splitArr.five] var completedTasks = 0 let totalTasks = arr.count for (i, value) in arr.enumerated() { let workItem = DispatchWorkItem { [weak self] in guard let self = self, !self.work[i]?.isCancelled ?? false else { return } let lang = Language.first() for (index, word) in value.enumerated() { // 每次循环前都检查任务是否被取消或test是否为true guard !self.work[i]?.isCancelled ?? false, !self.test else { break } self.anyTranslate(with: word, and: lang) } // 任务完成后计数,所有任务完成后再更新UI DispatchQueue.main.async { completedTasks += 1 if completedTasks == totalTasks { if self?.words.count == 0 { self?.noFound() } self?.tableView.reloadData() self?.tableView.isUserInteractionEnabled = true self?.indicator.stopAnimating() completion?() } } } work[i] = workItem DispatchQueue.global(qos: .userInteractive).async(execute: workItem) } }
3. 确保数组线程安全
给words数组添加读写锁,避免多线程同时修改导致的数据竞争:
private let wordsLock = NSLock() private var words = [Words]() // 修改addWord函数,加锁保护 func addWord(with word: Words) { wordsLock.lock() defer { wordsLock.unlock() } // 确保锁一定会被释放 words.append(word) }
4. 添加搜索防抖优化
快速输入时,延迟0.3秒再触发搜索,避免任务堆积:
private var debounceWorkItem: DispatchWorkItem? private var searchText = "" { willSet { debounceWorkItem?.cancel() work.forEach { $0?.cancel() } work = Array(repeating: nil, count: 5) test = true } didSet { test = false debounceWorkItem?.cancel() // 创建新的防抖任务 let workItem = DispatchWorkItem { [weak self] in guard let self = self else { return } if self.searchText.count >= 2 { self.search(nil) } else { DispatchQueue.main.async { self.words.removeAll() self.tableView.reloadData() self.noFound() self.tableView.isUserInteractionEnabled = true self.indicator.stopAnimating() } } } debounceWorkItem = workItem // 延迟0.3秒执行搜索 DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: workItem) } }
总结
按照上面的修改,你可以解决:
- 野指针访问导致的
[CFString length]崩溃 - 后台任务取消不彻底的问题
- 多线程数据竞争的风险
- 快速输入时的任务堆积问题
内容的提问来源于stack exchange,提问作者Vasya2014
相关产品推荐
相关产品推荐

