如何实现带完成回调的Swift函数按顺序执行并在成功时提前返回?
如何实现带完成回调的Swift函数按顺序执行并在成功时提前返回?
我完全懂你的需求——你希望这个fillField函数能按顺序逐个尝试CSS选择器,一旦某个选择器成功填充了字段(返回结果>0),就立刻停下不再尝试剩下的,直接给回调返回结果。原代码的问题在于,for循环会一股脑把所有evaluateJavaScript任务都调度出去,异步执行的特性让你根本没法中途终止,而且信号量在这里确实行不通——因为evaluateJavaScript必须在主线程执行,用信号量阻塞主线程会导致回调永远触发不了,直接死锁。
其实用递归就能完美解决这个问题,它能让我们在前一个异步任务完成后,再决定要不要继续试下一个选择器。下面是修改后的完整代码:
private func fillField(in webView: WKWebView, selectors: [String], value: String? = nil, checked: Bool? = nil, completion: @escaping (Int) -> Void) { // 先处理参数异常的情况 guard value != nil || checked != nil else { console.warning("No value or checked provided") completion(0) return } // 定义递归辅助函数,负责按顺序处理每个选择器 func tryNextSelector(at index: Int) { // 如果索引超出数组范围,说明所有选择器都试过了,返回0 guard index < selectors.count else { completion(0) return } let selector = selectors[index] let script: String if let value = value { script = String(format: "AutoFill.setValue('%@', '%@', '%@')", selector, "value", value) } else if let checked = checked { script = String(format: "AutoFill.setValue('%@', '%@', '%@')", selector, "checked", checked ? "true": "false") } else { // 这里前面已经guard过滤过,不会走到这一步 completion(0) return } console.info(script) webView.evaluateJavaScript(script) { result, error in // 检查是否成功填充了字段 if let fills = result as? Int, fills > 0 { // 成功了,直接调用完成回调,终止后续尝试 completion(fills) return } // 没成功(包括执行出错),继续尝试下一个选择器 tryNextSelector(at: index + 1) } } // 从第一个选择器开始尝试 tryNextSelector(at: 0) }
代码逻辑说明:
- 参数校验前置:先判断是否提供了
value或checked,避免后续做无效操作。 - 递归辅助函数:
tryNextSelector负责处理单个选择器,它接收当前尝试的索引:- 如果索引超出选择器数组长度,说明所有尝试都失败了,调用
completion(0)。 - 生成对应的JS脚本,调用
evaluateJavaScript执行注入。 - 在异步回调里判断结果:
- 如果成功填充了字段(
fills > 0),直接调用completion(fills),函数终止,不会再碰后续选择器。 - 如果失败(包括执行出错的情况),就递归调用自身,尝试下一个索引的选择器。
- 如果成功填充了字段(
- 如果索引超出选择器数组长度,说明所有尝试都失败了,调用
- 启动递归:在主函数里调用
tryNextSelector(at: 0),从第一个选择器开始顺序尝试。
这种方式完全符合你的需求:顺序执行、成功即停,而且完美适配evaluateJavaScript的异步特性,也不会阻塞主线程。
备注:内容来源于stack exchange,提问作者Martin Claesson
相关产品推荐
相关产品推荐

