用户登录
用户注册

分享至

weak_clear_no_lock 报错处理

  • 作者: 喜欢被宠着你
  • 来源: 51数据库
  • 2021-11-10

项目上线之后,通过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"];
    }
}

如有错误,烦请指正!

软件
前端设计
程序设计
Java相关