iOS开发Crash之weak_register_no_lock

1,456 阅读2分钟

今天遇到一个很神奇的事,从原生弹窗->跳转到小程序SDK->然后在小程序SDK里面的webView直接报错了. 查了半天原来是因为deinit里面中self已经在释放中了,不能被弱引用了。下面这个例子就是

protocol MyServiceDelegate: AnyObject {}

class MyService {
    weak var delegate: MyServiceDelegate?
    func stop() {}
}

class MyClass: NSObject, MyServiceDelegate {
    private lazy var service: MyService = {
        let service = MyService()
        service.delegate = self
        return service
    }()

    deinit {
        service.stop()
    }
}

// 测试
func test() {
    let myClass = MyClass()
}

其实 Crash 信息相对已经比较明显了,结合到代码就是 self 当前已经在释放中了(deinit),不可以被弱引用了(service.delegate = self)。

其实出现这个 Crash 有三个条件:

  • lazy
  • weak
  • NSObject 示例代码去除这三个条件中任何一个,Crash 都不会发生。

一般在dealloc中取weak self会引起这个现象,崩溃的堆栈为:

Thread 0 Crashed:
0 objc_crashlogPKc 
1 __objc_fatal 
2 _weak_register_no_lock 
3 _objc_storeWeak

我们看看将新的weak指针地址添加到弱引用中是如何实现的,即weak_register_no_lock方法

id weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, bool crashIfDeallocating)
{
    // referent_id是新的被弱引用对象
    objc_object *referent = (objc_object *)referent_id;
    
    // referrer_id是__weak指针的地址
    objc_object **referrer = (objc_object **)referrer_id;

    // 如果referent为nil 或 referent 采用了TaggedPointer计数方式,直接返回,不做任何操作
    if (!referent  ||  referent->isTaggedPointer()) return referent_id;

    // ensure that the referenced object is viable
    bool deallocating;
    // 确保被引用的对象可用(没有在析构,同时应该支持weak引用)
    if (!referent->ISA()->hasCustomRR()) {
        deallocating = referent->rootIsDeallocating();
    }
    else {
        BOOL (*allowsWeakReference)(objc_object *, SEL) = 
            (BOOL(*)(objc_object *, SEL))
            object_getMethodImplementation((id)referent, 
                                           @selector(allowsWeakReference));
        if ((IMP)allowsWeakReference == _objc_msgForward) {
            return nil;
        }
        deallocating =
            ! (*allowsWeakReference)(referent, @selector(allowsWeakReference));
    }

    // 正在析构的对象,不能够被弱引用
    if (deallocating) {
        if (crashIfDeallocating) {
            _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));
        } else {
            return nil;
        }
    }

    // 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(referent, referrer);
        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;
}

为什么去掉NSObject就不会错误了呢?