Shortcuts 中 AppleScript 因 UI 加载缓慢而中止,如何可靠等待?
结论
可以使用“条件轮询 + 短间隔 delay”:每隔一小段时间检查目标 UI 元素,元素出现后立即继续执行,无需事先猜测界面的完整加载时间。
原来的代码已经采用了条件轮询。需要调整检查间隔、目标元素的定位,以及等待失败时的处理。AppleEvent handler failed 这条错误本身不足以证明发生了超时,也不能保证仅修改 delay 就能解决问题。
原来的循环为什么仍可能失败?
原代码是:
repeat until UI element 1 exists
delay 0
end repeat
这里涉及两件事:循环等待元素出现,以及通过 System Events 查询界面。
如果查询正常返回 false,循环可以继续;如果查询本身抛出错误,脚本会在判断条件时中止,不会进入下一次等待。
此外,delay 0 没有提供实际的轮询间隔。改成例如 delay 0.1,可以避免在等待期间连续密集地查询 UI。元素出现后的额外等待通常约为一个轮询间隔,另加查询所需时间。
用短间隔轮询,并设置可选的等待上限
下面以等待 System Settings 的第一个窗口为例。运行前应确保应用已启动。实际使用时,将 exists window 1 换成对目标元素的检查。
-- 示例:最多等待 30 秒。
-- 如需持续轮询,可改为 missing value。
set maxWaitSeconds to 30
set startedAt to current date
repeat
if maxWaitSeconds is not missing value then
if ((current date) - startedAt) >= maxWaitSeconds then
error "等待 System Settings 界面超时。"
end if
end if
-- 为此处的 AppleEvent 调用设置超时。
with timeout of 10 seconds
tell application "System Events"
tell process "System Settings"
set targetReady to exists window 1
end tell
end tell
end timeout
if targetReady then exit repeat
delay 0.1
end repeat
-- 在这里继续执行需要该窗口的操作。
30 秒和 10 秒都是示例值,应按实际情况调整。代码没有吞掉 UI 查询异常:如果 System Events 查询失败,错误会保留下来,便于定位问题。
窗口出现后,里面的按钮、列表或其他控件不一定已经准备好。后续需要点击哪个控件,就应尽量检查该控件,必要时还要检查它是否可用。如果目标路径包含尚未出现的父容器,应先确认父容器存在,再查询其子元素。
区分两种超时
示例中有两种独立的限制:
maxWaitSeconds:由脚本主动检查的总等待上限。with timeout:影响块内需要等待响应的 AppleEvent 调用,不是给整个循环设置统一的倒计时。
如果某次查询迟迟没有返回,脚本也无法同时检查总等待时间。因此,总耗时可能超过 maxWaitSeconds,直到当前查询返回或报错。
如果确认是 AppleEvent 等待响应超时,可以按需增大 with timeout 的值。但它不会延长 Shortcuts 或其他调用层可能施加的执行限制,也不能修复应用无响应或失效的 UI 路径。
把 maxWaitSeconds 改为 missing value,只表示取消这段循环自行设置的总等待上限。查询错误、单次 AppleEvent 超时或外部中止仍可能结束执行,不能保证无限等待。
如果仍然出现相同错误
先记录错误发生在哪一次 UI 查询,同时保留错误编号。可以临时在可疑查询外加上:
try
-- 放入实际的 UI 查询代码。
on error errMsg number errNum
error ("UI 查询失败,错误编号 " & errNum & ": " & errMsg) number errNum
end try
然后核对目标进程、窗口和控件路径是否仍然有效。使用 UI element 1 时,尤其要确认界面切换后第一个元素是否仍是预期对象。如果能使用明确的控件类型和标识,定位通常更容易维护。
不建议忽略所有查询错误并无限重试,否则权限问题、错误路径和应用退出等情况都会表现为一直等待。只有确认某种错误属于加载过程中的暂时状态,才适合对该错误做有限重试。
备注:内容仅供参考。
