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

使用DispatchQueue重放鼠标移动时卡顿的问题排查与解决方案咨询

问题分析与解决方案

你的Swift代码卡顿的核心问题出在**DispatchQueue.global().asyncAfter的延迟计算逻辑**,以及事件调度的方式上,我们一步步拆解:


为什么原Swift代码会卡顿?

你当前的代码是给每个鼠标移动任务设置了now() + movement.time的延迟,这意味着所有任务的延迟都是相对于循环执行时的当前时间,而不是相对于前一个任务完成的时间。举个例子:

  • 第一个任务延迟100ms,第二个延迟200ms,第三个延迟150ms
  • 这三个任务会分别在循环启动后的100ms、200ms、150ms时同时(或几乎同时)触发
  • 系统瞬间收到大量鼠标移动事件,根本处理不过来,所以只能显示少量点位,出现卡顿

而你的Node.js代码之所以流畅,是因为Promise.all虽然同时启动了所有setTimeout,但如果你的录制数据中move.time是从录制开始的累计绝对时间(比如第一个是100ms,第二个是300ms,第三个是450ms),那么每个任务的延迟是按时间线依次触发的,不会出现并发事件堆积。


为什么usleep版本速度慢4倍?

你用usleep时,大概率犯了两个错误:

  1. 时间单位混淆:usleep的参数是微秒(1ms=1000us),如果你直接传入movement.time(毫秒值),实际等待时间只有预期的1/1000,理论上应该更快,但你说慢4倍,那可能是第二个问题导致的。
  2. 阻塞主线程:如果在主线程调用usleep,会阻塞整个UI事件循环,系统处理鼠标事件的优先级被降低,导致每个动作的实际执行间隔被拉长,整体速度变慢。

修正后的Swift实现方案

方案1:使用累计延迟的异步调度(推荐)

计算从当前时间开始的累计延迟,确保每个鼠标移动任务按时间线依次触发,同时用主队列调度(避免后台队列的调度延迟):

var cumulativeDelay: TimeInterval = 0.0
let mainQueue = DispatchQueue.main

for movement in movements {
    // 把毫秒转成TimeInterval需要的秒数
    cumulativeDelay += TimeInterval(movement.time) / 1000.0
    
    mainQueue.asyncAfter(deadline: .now() + cumulativeDelay) {
        let point = CGPoint(x: Int(movement.x), y: Int(movement.y))
        // 安全解包,避免强制解包崩溃
        guard let moveEvent = CGEvent(
            mouseEventSource: nil,
            mouseType: .mouseMoved,
            mouseCursorPosition: point,
            mouseButton: .left
        ) else {
            return
        }
        moveEvent.post(tap: CGEventTapLocation.cghidEventTap)
    }
}

方案2:使用后台串行队列+usleep(适合需要严格时序的场景)

在后台串行队列中执行,避免阻塞主线程,同时修正时间单位:

// 创建一个串行队列,确保任务按顺序执行
let mouseMoveQueue = DispatchQueue(label: "com.your.app.mouse-move-queue")

mouseMoveQueue.async {
    for movement in movements {
        // 把毫秒转成微秒,传给usleep
        usleep(UInt32(movement.time) * 1000)
        
        // 回到主队列发送鼠标事件(CGEvent.post在主线程更稳定)
        DispatchQueue.main.sync {
            let point = CGPoint(x: Int(movement.x), y: Int(movement.y))
            guard let moveEvent = CGEvent(
                mouseEventSource: nil,
                mouseType: .mouseMoved,
                mouseCursorPosition: point,
                mouseButton: .left
            ) else {
                return
            }
            moveEvent.post(tap: CGEventTapLocation.cghidEventTap)
        }
    }
}

额外优化建议

  • 避免强制解包CGEvent:如果创建事件失败,强制解包会导致崩溃,用guard let处理更安全。
  • 优先使用主队列调度鼠标事件:虽然CGEvent是全局事件,但在主队列执行能避免后台队列的调度波动,让移动更流畅。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 20:52:47