ios-webview-dispatch_async主线程调用alert卡死

问题描述

iOS里做webview调用js方法的时候遇到一个问题, 调用的js方法里面有一句alert(), 结果导致界面卡死, 无法点击. 代码类似如下:

1
2
3
dispatch_async(dispatch_get_main_queue(), ^{
[self.webpageView stringByEvaluatingJavaScriptFromString:@"alert(1)"];
});

如何直接调用:

1
[self.webpageView stringByEvaluatingJavaScriptFromString:@"alert(1)"];

不会卡死, 但我们的需求是这段代码必须放在主线程.

最后解决办法是这样:

1
[self.webpageView performSelectorOnMainThread:@selector(stringByEvaluatingJavaScriptFromString:) withObject:@"alert(1)" waitUntilDone:NO];

那么, 为什么造成卡死呢?

我的分析

先看dispatch_async的实现中,任务执行代码:
GCD代码片段
dispatch_atomic_incdispatch_atomic_dec是原子操作函数.

由代码可知, 在dispatch_async执行任务的时候会对当前工作的线程加原子操作.
什么是原子操作
原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切[1] 换到另一个线程),也就是说, 任务不执行完, 工作线程是不会被中断的.

在我们的代码中对主线程进行了原子操作, 这时任务执行不完,主线程不会被中断.

在JS中,我们知道在js中alert具有中断代码的作用, alert下面的代码在alert点击OK之前是不会被执行到的.

然而,在iOS里面, 所有的UI操作都是在主线程中进行的, alert也不例外(至少点击alert的OK操作是需要主线程的), 这时alert需要主线程去进行UI操作,但是主线程被dispatch_async加了原子操作, 所有alert拿不到主线程的操作权, 但是dispatch_async又要等到任务执行完才能释放主线程, 所以造成了死循环.(dispatch_async拿着主线程不释放,等着js执行完才能释放, alert需要主线程才能执行操作,alert下面的代码无法执行到.)

分析解决办法

1
[self.webpageView performSelectorOnMainThread:@selector(stringByEvaluatingJavaScriptFromString:) withObject:@"alert(1)" waitUntilDone:NO];

官方注释wait才是的意思是:
A Boolean that specifies whether the current thread blocks until after the specified selector is performed on the receiver on the main thread. Specify YES to block this thread; otherwise, specify NO to have this method return immediately.
大致意思是:
如果wait是yes,会锁死主线程,直到selector方法执行完. 如果为no,selector方法会立即返回.

SO
造成这个问题的原因是:竞争主线程,造成界面卡死.

参考资料

GCD 源码
dispatch_async 的分析