You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于UITextView的代码编辑器着色卡顿及异步失效问题求助

看起来你遇到了UITextView代码着色卡顿的问题,而且尝试多线程优化时还没效果,我来帮你分析下问题根源,再给你几个可行的优化方案。

首先说下多线程没效果的核心原因:

  1. 你可能在后台线程操作了UI相关的最终赋值——设置textView.attributedText必须在主线程执行,哪怕前面的着色逻辑在后台完成,最后一步也得切回主线程。
  2. wordToColor作为全局变量,读写没有线程安全保护:如果后台线程读取它的时候,主线程正在动态修改,会导致数据不一致,自然看不到着色效果。

再说说性能卡顿的关键问题:
你的当前实现有几个效率极低的点:

  1. 每次文本变化都重新生成完整的NSMutableAttributedString,且每个关键词单独遍历整个文本,关键词越多,重复遍历的次数就越多,耗时呈线性增长。
  2. 用递归方式查找关键词,递归本身有额外的栈开销,还容易出现不必要的重复计算。
  3. NSString和Swift String的频繁转换,也会带来额外的性能损耗。

方案一:用正则表达式批量匹配,大幅减少遍历次数

把所有需要着色的关键词拼成一个正则表达式,一次遍历就能匹配所有关键词的位置,统一完成着色,比单个关键词逐个遍历高效得多。

代码示例:

var wordToColor = [String: UIColor]()
// 用串行队列保证wordToColor的线程安全读写
private let wordSyncQueue = DispatchQueue(label: "com.yourapp.wordColorQueue")

func textViewDidChange(_ textView: UITextView) {
    updateLineText()
}

func updateLineText() {
    // 防抖处理:用户连续打字时,延迟0.1秒再执行着色,避免频繁触发耗时操作
    NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(performColoring), object: nil)
    perform(#selector(performColoring), with: nil, afterDelay: 0.1)
}

@objc private func performColoring() {
    let currentContent = content
    // 后台线程处理耗时的着色逻辑
    DispatchQueue.global().async { [weak self] in
        guard let self = self else { return }
        
        // 线程安全读取wordToColor
        let wordMap = self.wordSyncQueue.sync { self.wordToColor }
        if wordMap.isEmpty {
            DispatchQueue.main.async {
                self.textView.attributedText = NSAttributedString(string: currentContent)
            }
            return
        }
        
        // 构建正则表达式:转义关键词中的特殊字符,用|分隔所有关键词
        let escapedWords = wordMap.keys.map { NSRegularExpression.escapedPattern(for: $0) }
        let pattern = "\\b(" + escapedWords.joined(separator: "|") + ")\\b"
        guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {
            DispatchQueue.main.async {
                self.textView.attributedText = NSAttributedString(string: currentContent)
            }
            return
        }
        
        let mutableAttrString = NSMutableAttributedString(string: currentContent)
        let fullRange = NSRange(location: 0, length: currentContent.utf16.count)
        
        // 遍历所有匹配结果,统一着色
        regex.enumerateMatches(in: currentContent, options: [], range: fullRange) { result, _, stop in
            guard let matchRange = result?.range else { return }
            let matchedWord = (currentContent as NSString).substring(with: matchRange)
            
            if let color = wordMap[matchedWord] {
                mutableAttrString.addAttribute(.foregroundColor, value: color, range: matchRange)
                
                // 检查关键词前是否有.,有则改成紫色
                if matchRange.location > 0 {
                    let dotRange = NSRange(location: matchRange.location - 1, length: 1)
                    let dotChar = (currentContent as NSString).substring(with: dotRange)
                    if dotChar == "." {
                        mutableAttrString.addAttribute(.foregroundColor, value: UIColor.purple, range: matchRange)
                    }
                }
            }
        }
        
        // 回到主线程更新UI,同时检查文本是否一致(避免用户在处理过程中修改了文本)
        DispatchQueue.main.async {
            if self.content == currentContent {
                self.textView.attributedText = mutableAttrString
            }
        }
    }
}

方案二:优化原有逻辑的线程安全与执行效率

如果你更倾向于保留原有单个关键词处理的逻辑,可以做以下调整:

  1. 用串行队列保证wordToColor的线程安全。
  2. 把递归改成迭代,避免栈开销。
  3. 后台线程只负责构建NSMutableAttributedString,最终UI赋值必须在主线程。

代码示例:

var wordToColor = [String: UIColor]()
private let wordSyncQueue = DispatchQueue(label: "com.yourapp.wordColorQueue")

func textViewDidChange(_ textView: UITextView) {
    updateLineText()
}

func updateLineText() {
    NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(performColoring), object: nil)
    perform(#selector(performColoring), with: nil, afterDelay: 0.1)
}

@objc private func performColoring() {
    let currentContent = content
    DispatchQueue.global().async { [weak self] in
        guard let self = self else { return }
        
        let wordMap = self.wordSyncQueue.sync { self.wordToColor }
        let mutableAttrString = NSMutableAttributedString(string: currentContent)
        
        for word in wordMap.keys {
            self.applyColoring(to: mutableAttrString, word: word, content: currentContent)
        }
        
        DispatchQueue.main.async {
            if self.content == currentContent {
                self.textView.attributedText = mutableAttrString
            }
        }
    }
}

// 把递归改成迭代,避免栈开销
private func applyColoring(to string: NSMutableAttributedString, word: String, content: String) {
    var startLocation = 0
    let contentLength = content.utf16.count
    let wordLength = word.utf16.count
    
    while startLocation <= contentLength - wordLength {
        let searchRange = NSRange(location: startLocation, length: contentLength - startLocation)
        let foundRange = (content as NSString).range(of: word, options: [], range: searchRange)
        
        if foundRange.location == NSNotFound {
            break
        }
        
        if self.rangeCheck(range: foundRange) {
            if let color = wordToColor[word] {
                string.addAttribute(.foregroundColor, value: color, range: foundRange)
                
                if foundRange.location > 0 {
                    let index0 = content.index(content.startIndex, offsetBy: foundRange.location - 1)
                    if content[index0] == "." {
                        string.addAttribute(.foregroundColor, value: UIColor.purple, range: foundRange)
                    }
                }
            }
        }
        
        startLocation = foundRange.location + foundRange.length
    }
}

额外优化建议

  • 防抖处理:上面代码中加入了延迟执行和取消之前请求的逻辑,能避免用户连续打字时频繁触发着色操作,大幅减少性能消耗。
  • 文本一致性检查:后台处理完成后,回到主线程要检查当前文本是否和处理时一致,避免设置过时的着色内容。
  • 统一范围处理:尽量用utf16.count计算NSRange,因为NSRange是基于UTF-16编码的,和NSString的长度逻辑一致,减少转换开销。

内容的提问来源于stack exchange,提问作者Anters Bear

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 03:54:57