sdk 中涉及到 UI 操作的时候,一定要注意线程问题!一定要注意线程问题!一定要注意线程问题!
从最初开始学习 iOS 的时候,我们就被告知 UI 操作一定要放在主线程进行,这是因为 UIKit 的方法不是线程安全的,保证线程安全需要极大的开销。
试想一下几种场景:
- 两个线程同时设置同一个背景图片,则很有可能因为当前图片被释放了两次而导致应用崩溃;
- 两个线程同时设置同一个 UIView 的背景色,则很有可能渲染显示的是颜色 A,而此时在 UIView 逻辑树上的背景颜色属性为 B;
- 两个线程同时操作 View 的树形结构:在线程 A 中 for 循环遍历并操作当前 View 的所有 subView,而此时线程 B 中将某个 subView 直接删除,这就导致了错乱,甚至导致应用崩溃。
出了什么问题?
最近 hybrid sdk 收尾,偶然发现线上有一类 crash 最近两个版本稳步上升,而且可以肯定的是我负责的 sdk 提供的 web 容器导致的,__ZL17_WebTryThreadLockb 是函数调用栈最后调用的 api。
crash 线程调用栈详细如下:
Thread 17 Crashed:
0 WebCore __ZL17_WebTryThreadLockb
1 WebCore _WebThreadLock
2 UIKit -[UIWebBrowserView resignFirstResponder]
3 UIKit -[UIResponder _resignIfContainsFirstResponder]
4 UIKit -[UIView(Hierarchy) _willMoveToWindow:]
5 UIKit -[UIView(Internal) _addSubview:positioned:relativeTo:]
6 UIKit ___53-[_UINavigationParallaxTransition animateTransition:]_block_invoke_2
7 UIKit +[UIView(Animation) performWithoutAnimation:]
8 UIKit ___53-[_UINavigationParallaxTransition animateTransition:]_block_invoke
9 UIKit +[UIView(Internal) _performBlockDelayingTriggeringResponderEvents:]
10 UIKit -[_UINavigationParallaxTransition animateTransition:]
11 UIKit -[UINavigationController _startCustomTransition:]
12 UIKit -[UINavigationController _startDeferredTransitionIfNeeded:]
13 UIKit -[UINavigationController __viewWillLayoutSubviews]
14 UIKit -[UILayoutContainerView layoutSubviews]
15 UIKit -[UIView(CALayerDelegate) layoutSublayersOfLayer:]
16 QuartzCore -[CALayer layoutSublayers]
17 QuartzCore __ZN2CA5Layer16layout_if_neededEPNS_11TransactionE
18 QuartzCore __ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE
19 QuartzCore __ZN2CA7Context18commit_transactionEPNS_11TransactionE
20 QuartzCore __ZN2CA11Transaction6commitEv
21 QuartzCore __ZN2CA11Transaction14release_threadEPv
22 libsystem_pthread.dylib __pthread_tsd_cleanup
23 libsystem_pthread.dylib __pthread_exit
24 libsystem_pthread.dylib __pthread_wqthread
25 libsystem_pthread.dylib _start_wqthread
原因是什么?
Stack Overflow 上的问题和解答贴出两个大家一起参考:
app-crash-no-idea-why
ios-webtrythreadlock-crash
根本原因:Tried to obtain the web lock from a thread other than the main thread or the web thread.
问题定位
ABAuthorizationStatus authStatus = ABAddressBookGetAuthorizationStatus();
if (authStatus == kABAuthorizationStatusNotDetermined)
{
//获取授权
ABAddressBookRef addressBook = ABAddressBookCreate();
ABAddressBookRequestAccessWithCompletion( addressBook, ^(bool granted, CFErrorRef error) {
if (granted)
{
[self openContact];
}
else
{
[self showAlertView];
}
});
}
之前草率的确定为上述代码问题,上述代码确实存在两个问题
- 内存泄漏
- 非主线程调用 UI 操作
但重新看 crash 调用栈,确定并不会是 AddressBook 导致,而真实原因已无从考证。
更新文章,留作后人查阅。