weak_clear_no_lock 报错处理
项目上线之后,通过firbase追踪发现有一个异常报错,闪退率惊人,起初我们以为是firebase同步了WKWebView的异常信息导致的,但后来在本人手机上复测发现实际上不是这个问题,是真正Bug存在。
Crashed: com.apple.main-thread
0 libsystem_kernel.dylib 0x1af90d858 __abort_with_payload + 8
1 libsystem_kernel.dylib 0x1af911ed8 abort_with_payload_wrapper_internal + 104
2 libsystem_kernel.dylib 0x1af911e70 abort_with_payload_wrapper_internal + 30
3 libobjc.A.dylib 0x1af85a3cc _objc_fatalv(unsigned long long, unsigned long long, char const*, char*) + 108
4 libobjc.A.dylib 0x1af85a360 _objc_fatalv(unsigned long long, unsigned long long, char const*, char*) + 30
5 libobjc.A.dylib 0x1af856530 weak_clear_no_lock + 322
6 libobjc.A.dylib 0x1af857f70 objc_storeWeak + 336
7 WebKit 0x1b743d5b0 WebKit::UIDelegate::setDelegate(id<WKUIDelegate>) + 32
8 WebKit 0x1b71d2acc -[WKWebView setUIDelegate:] + 48
9 XXX 0x1053e343c -[XXXWebViewController webView] + 426 (XXXWebViewController.m:426)
10 XXX 0x1053e3318 -[XXXWebViewController dealloc] + 416 (XYFWebViewController.m:416)
11 XXX 0x1053d97f4 -[XXXManager viewPageChangeWithTarget:actionItem:complete:] + 138 (XXXManager.m:138)
12 XXX 0x1053d9530 __54-[XXXManager startLoanWithTarget:actionItem:complete:]_block_invoke.115 + 120 (XXXManager.m:120)
13 XXX 0x1053e6740 __68-[XXXNetClient getUserObjectAfterloginSuccessedWithTarget:callback:]_block_invoke + 232 (XXXNetClient.m:232)
14 libdispatch.dylib 0x1af7c6ec4 _dispatch_call_block_and_release + 32
15 libdispatch.dylib 0x1af7c833c _dispatch_client_callout + 20
看日志是不是以为WKWebView的问题,实际上经过调试发现,是因为当前controller释放的时候,又在dealloc调用了WKWebView设置UIDelegate的方法,但是因为当前controller正在被释放中,所以产生了weak_clear_no_lock报错。报错打印信息如下:
Cannot form weak reference to instance (0x160f6f890) of class XXXWebViewController. It is possible that this object was over-released, or is in the process of deallocation.
场景:登录页面是present出来的,登录完毕后,页面dismiss消失,然后跳转到webView页面,但线上的原因是因为线回调了登录成功block去跳转webview,然后才dismiss页面,由于登录页面没有导航,所以无法跳转webview页面,但是创建了webView页面的controller,dismiss后,刚才创建的webView的controller就要被dealloc,但是在dealloc的时候,在webview的懒加载代理里又设置为代理。此时便会报错。
//MARK: -- Remove Delegate
- (void)dealloc {
[self.webView removeObserver:self forKeyPath:@"estimatedProgress"];
[self.webView removeObserver:self forKeyPath:@"title"];
}
- (WKWebView *)webView {
if (!_webView) {
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
_webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:config];
_webView.UIDelegate = self;
_webView.navigationDelegate = self;
// 设置UserAgent
[_webView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id result, NSError *error) {
NSString *userAgent = result;
NSString *preStr = [NSString stringWithFormat:@"%@-iOS", [XXXNetClient sharedClient].appname?:@""];
if (![userAgent hasPrefix:preStr]) {
NSString *customUserAgent = [preStr stringByAppendingString:userAgent];
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:customUserAgent, @"UserAgent", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];
[[NSUserDefaults standardUserDefaults] synchronize];
}
}];
}
return _webView;
}
weak_clear_no_lock是内存管理时释放weak指针清理资源时用到的函数,我们找来它的源码如下:
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
if (!referent || referent->isTaggedPointer()) return referent_id;
// ensure that the referenced object is viable
bool deallocating;
if (!referent->ISA()->hasCustomRR()) {
deallocating = referent->rootIsDeallocating();
}
else {
BOOL (*allowsWeakReference)(objc_object *, SEL) =
(BOOL(*)(objc_object *, SEL))
object_getMethodImplementation((id)referent,
SEL_allowsWeakReference);
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
}
if (deallocating) {
_objc_fatal("Cannot form weak reference to instance (%p) of "
"class %s. It is possible that this object was "
"over-released, or is in the process of deallocation.",
(void*)referent, object_getClassName((id)referent));
}
// now remember it and where it is being stored
weak_entry_t *entry;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}
else {
weak_entry_t new_entry;
new_entry.referent = referent;
new_entry.out_of_line = 0;
new_entry.inline_referrers[0] = referrer;
for (size_t i = 1; i < WEAK_INLINE_COUNT; i++) {
new_entry.inline_referrers[i] = nil;
}
weak_grow_maybe(weak_table);
weak_entry_insert(weak_table, &new_entry);
}
// Do not set *referrer. objc_storeWeak() requires that the
// value not change.
return referent_id;
}
runtime 是通过检查引用计数的个数来判断对象是否在 deallocting, 然后通过deallocting来使得程序报错。问题就是这样,涉及到的Weak的实现方式可以自行了解。
解决方案1:登录页面dismiss之后再回调登录成功的block
解决方案2: webview的controller在dealloc的时候不使用点语法,可以改为以下方式,以避免程序crash
- (void)dealloc {
if (_webView) {
[_webView removeObserver:self forKeyPath:@"estimatedProgress"];
[_webView removeObserver:self forKeyPath:@"title"];
}
}
如有错误,烦请指正!
本文地址:https://blog.csdn.net/HelloWorld_1986/article/details/107183215