在这里主要分析使用UIWebView导致产生crash的原因。
从我们项目的crash日志收集平台中,有诸多如下的日志:
Application received signal 11
1 libobjc.A.dylib 0x1800bc150 _objc_msgSend + 16
2 UIKit 0x18799156c -[UIWebView webView:resource:didFinishLoadingFromDataSource:] + 84
3 CoreFoundation 0x18164ce80 ___invoking___ + 144
4 CoreFoundation 0x1815422c4 -[NSInvocation invoke] + 292
5 CoreFoundation 0x181546e9c -[NSInvocation invokeWithTarget:] + 60
6 WebKitLegacy 0x1873d4820 <redacted>
Application received signal 11
崩溃线程
WebThread
格式化
1 libobjc.A.dylib 0x1800bc150 _objc_msgSend + 16
2 UIKit 0x187c33354 -[UIWebView webThreadWebView:resource:willSendRequest:redirectResponse:fromDataSource:] + 92
3 WebKitLegacy 0x187477e60 <redacted>
4 WebKitLegacy 0x1873d315c <redacted>
5 WebCore 0x1861cdee0 WebCore::ResourceLoadNotifier::dispatchWillSendRequest(WebCore::DocumentLoader*, unsigned long, WebCore::ResourceRequest&, WebCore::ResourceResponse const&) + 232
6 WebCore 0x186f1c494 WebCore::ResourceLoader::willSendRequestInternal(WebCore::ResourceRequest&, WebCore::ResourceResponse const&) + 556
7 WebCore 0x18703cbb0 WebCore::SubresourceLoader::willSendRequestInternal(WebCore::ResourceRequest&, WebCore::ResourceResponse const&) + 252
8 WebCore 0x1861cd040 WebCore::ResourceLoader::init(WebCore::ResourceRequest const&) + 288
9 WebCore 0x1861ccdec WebCore::SubresourceLoader::startLoading() + 36
10 WebKitLegacy 0x1874b23b0 <redacted>
以前在看到这种日志的时候,就在想,signal 11
崩溃日志不明确,或者说WebView的系统代码,不是我们自己的代码改不了的种种。。。
不过现在从crash统计平台的数据看来数量相当大,是非常影响用户的体验的。所以今天就抽时间来分析分析其原因。
从日志上看,基本都是Application received signal 11
,那么signal 11
是代表什么呢?
于是翻看系统的头文件sys/signal.h
, 我们可以发现里面有一个宏定义#define SIGSEGV 11 /* segmentation violation */
, 这个SIGSEGV
其实就是signal 11
的宏定义,注释很明确了:无效的内存引用
那么问题就来了,WebCore内部发出无效内存引用,我们好像也无能为力啊???咋办呢?
这时候就去看看UIWebView有没有其它什么可能的原因,发现在头delegate的定义
@property (nullable, nonatomic, assign) id <UIWebViewDelegate> delegate;
我们都知道assign
一般是用于元类型,delegate在ARC下一般是被声明为weak
。
这就奇怪了,怎么这么声明呢? 因此带着疑问去看UIWebView的文档:
Protocol
UIWebViewDelegate
The UIWebViewDelegate protocol defines methods that a delegate of a
UIWebView
object can optionally implement to intervene when web content is loaded.
SDK
iOS 2.0+
Framework
UIKit
On This Page
Overview
Topics
Relationships
See Also
Overview
> Important
> Before releasing an instance of UIWebView for which you have set a delegate,
> you must first set the UIWebView delegate property to nil before disposing of the UIWebView instance.
> This can be done, for example, in the dealloc method where you dispose of the UIWebView.`
>
意思就是: 在释放一个你已经为其设过 delegate 的 UIWebView 实例之前,你首先一定要将该 UIWebView 对象的 delegate 属性设为 nil。比如说,你可以在你的 dealloc 方法中这样做。
这时候就恍然大悟了,我们的项目中对WebView的使用并没有如此!!! 因为在UIWebView的delgate属性为assign 在被销毁的时候delegate不会被设为nil,导致WebView回调的时候引用的delegate已经是无效的内存指针了,因为指针指向的内存已经被释放,但指针没有被置空,这也正是文档里面为什么要重要强调需要在销毁前设置为nil的原因。
这也解释了为什么crash日志中总是收到莫名的WebView的crash,而且都是Application received signal 11
(即无效的内存引用)
到这里,相信大家都应该知道什么原因了,修改 UIWebView 的 delegate 的对象的 dealloc 方法中添加 _webView.delegate = nil; 如下:
- (void)dealloc{
/*
Important
Before releasing an instance of UIWebView for which you have set a delegate,
you must first set the UIWebView delegate property to nil before disposing of the UIWebView instance.
This can be done, for example, in the dealloc method where you dispose of the UIWebView.
*/
if (self.webView.loading) {
[self.webView stopLoading];
}
self.webView.delegate = nil;
}
ps:由于很少写文章,所以写出来的都是没深度没难度的流水帐。这里也就记录一下这个过程,以便以后遇到类似问题能够有不至于无从下手。如有错误,望看官们不吝指正。万分感激!