Сбой JavacriptCore WebKit EXC_BAD_ACCESS после выполнения обратного вызова с данными из фонового потока

В настоящее время я пытаюсь отладить сбои в реализации JavascriptCore интерфейса для собственного кода, чтобы выполнить некоторую работу от имени кода javascript в WebView.

Иногда сбой происходит очень скоро после запуска приложения, в других случаях для его возникновения может потребоваться несколько минут выполнения сотен вызовов собственного кода.

Это две верхние строки обратной трассировки каждого сбоя:

(lldb) thread backtrace
* thread #1: tid = 0x37960c, 0x00007fff8de6ecca JavaScriptCore`sanitizeStackForVMImpl + 15, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x700001a2c000)
    frame #0: 0x00007fff8de6ecca JavaScriptCore`sanitizeStackForVMImpl + 15

Вот упрощенная версия моего контроллера представления:

class MyViewController: NSViewController, WebFrameLoadDelegate {

    let worker = Worker() 

    // other setup code...

    func webView(webView: WebView!, didCreateJavaScriptContext context: JSContext!, forFrame frame: WebFrame!) {
        context.setObject(worker, forKeyedSubscript: "ClientWorker")
    }
}

Сам протокол JSExport и реализация кода, выполняющего работу. Для тестирования я удалил фактическую работу и просто вернул словарь с фиктивными данными, и сбой все еще происходит.

@objc protocol WorkerJSExports: JSExport {
    func doWork(params: [String:AnyObject], callback: JSValue)
}

@objc class Worker: NSObject, WorkerJSExports {

    func doWork(params: [String:AnyObject], callback: JSValue) {
        executeBackground(callback) {
            return [
                "test": "data"
            ]
        }
    }

    private func executeBackground(callback: JSValue!, f: ()->([String:AnyObject])) {
        dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)) {
            let result = f()
            dispatch_sync(dispatch_get_main_queue()) {
                self.executeCallback(callback, result: result)
            }
        }
    }

    private func executeCallback(callback: JSValue!, result: AnyObject) {
        callback.context.evaluateScript("setTimeout").callWithArguments([callback, 0, result])
    }
}

Методы executeBackground и executeCallback являются вспомогательными функциями, а executeCallback использует setTimeout в ответ на то, что я прочитал в этом сообщении/ответе SO: JavaScriptCore -- Передача функции в качестве параметра в ObjC, похоже, устранила другие сбои, связанные с блокировкой.

Когда я заменил executeBackground на следующую функцию, которая выполняется только в основном потоке, я не смог воспроизвести сбой:

private func executeMain(callback: JSValue!, f: ()->([String:AnyObject])) {
    dispatch_async(dispatch_get_main_queue()) {
        self.executeCallback(callback, result: f())
    }
}

Кажется, что есть какая-то проблема, возникающая при передаче данных, созданных в фоновом потоке, в WebView, но после просмотра документации я не уверен, что может быть. Единственное табу, о котором я упоминал, это передача данных между несколькими экземплярами JSVirtualMachine, что не кажется применимым, поскольку я взаимодействую только с одним экземпляром WebView. Любая помощь в выяснении этого очень ценится!


Обновлять

Кажется, я решил проблему, отключив использование Grand Central Dispatch непосредственно для NSOperationQueues. После изменения executeBackground на следующее сбои больше не повторялись.

private let workerQueue = NSOperationQueue()

private func executeAsync(callback: JSValue!, f: ()->([String:AnyObject])) {
    self.workerQueue.addOperationWithBlock({
        let result = f()
        NSOperationQueue.mainQueue().addOperationWithBlock({
            self.executeCallback(callback, result: result)
        })
    })
}

Хотя я не могу на самом деле доказать, что это устранило сбой, мы провели довольно обширное тестирование этой функции и больше ее не видели. Причина, по которой я не опубликовал этот ответ на свой собственный вопрос, заключается в том, что я не понимаю, почему именно это отличается и/или лучше. Если у кого-нибудь есть представление о том, что могло решить изменение в NSOperationQueues, это было бы очень признательно!


person Aaron    schedule 15.07.2016    source источник
comment
Что вы делаете с dispatch_sync(dispatch_get_main_queue()) в функции executeBackground. Должно быть dispatch_async(dispatch_get_main_queue())   -  person KrishnaCA    schedule 22.11.2016
comment
Я не имел дело с этим фрагментом кода некоторое время, но навскидку я не уверен! Тем не менее, можете ли вы пояснить, что неверно в использовании dispatch_sync в основной очереди?   -  person Aaron    schedule 23.11.2016