如何使用完成处理程序按顺序执行函数?

How to execute function with completion handler sequentially?

提问人:Martin Claesson 提问时间:7/20/2023 最后编辑:HangarRashMartin Claesson 更新时间:7/21/2023 访问量:44

问:

我有一个 Swift 函数,它使用 JavaScript 注入根据给定的 CSS 选择器填充 WKWebView 中的表单字段。JS 函数返回已归档字段的数量。我想修改 Swift 函数,以便它按顺序尝试每个选择器,如果其中一个选择器成功填充了一个字段(或多个字段),该函数应该提前返回,而无需尝试其余的选择器。fillField

例如,函数由以下方式触发:

fillField(in: webView, selectors: ["input[autocomplete=\"username\"]", "input[name=\"username\" i]"], value: "test_value") { fills in
    console.debug("Auto-filled \(fills) fields")
}

下面是 fillField 函数的现有代码:

private func fillField(in webView: WKWebView, selectors: [String], value: String? = nil, checked: Bool? = nil, completion: @escaping (Int) -> Void) {
    for selector in selectors {
        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 {
            console.warning("No value or checked provided")
            completion(0)
            return
        }
        
        console.info(script)
        
        webView.evaluateJavaScript(script) { result, error in
            if let result = result as? Int {
                // Result = number of filed fields
                if result > 0 {
                    // STOP EXECUTING
                }
            }
        }
    }
    completion(0)
    return
}

我想确保一旦选择器成功填充一个字段(或多个字段),该函数应停止处理并提前返回,从而避免对其余选择器进行不必要的评估。

我尝试使用但它不起作用,因为需要在主线程中执行。DispatchSemaphoreevaluateJavaScript

任何关于如何解决的想法都非常感谢。

iOS Swift 异步 同步 WKWebView

评论


答:

0赞 Claudio 7/21/2023 #1

尝试这种方法,想法是使用 DispatchGroup 并使循环按顺序运行,因为 JS 是一种单线程语言,它应该不是问题,并且像以前一样工作。代码不能在主线程上运行,因为 wait() 会阻止它,因此它应该在另一个线程上运行,等待在主线程上运行的代码完成。此方法使用 DispatchGroup,但 DispatchSemaphore 也应该有效。

private func fillField(in webView: WKWebView, selectors: [String], value: String? = nil, checked: Bool? = nil, completion: @escaping (Int) -> Void) {
    DispatchQueue.global(qos: .userInitiated).async {
        let dispatchGroup = DispatchGroup()

        for selector in selectors {
            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 {
                console.warning("No value or checked provided")
                completion(0)
                return
            }

            console.info(script)

            dispatchGroup.enter()
            var shouldStopLoop = false

            webView.evaluateJavaScript(script) { result, error in
                if let result = result as? Int {
                    // Result = number of filed fields
                    if result > 0 {
                        // STOP EXECUTING
                        shouldStopLoop = true
                    }
                }
                dispatchGroup.leave()
            }

            dispatchGroup.wait() // can set a timeout

            if shouldStopLoop {
                break // stop loop or call completion
            }
        }

        // maybe switch dispatchqueue before calling this
        completion(0)
    }
}