iOS
Objective-C
Swift

Objective-C 在对象deinit方法中,使用其weak引用导致crash。

简介:在对象正在释放的过程中,或者对象已经释放后,是不允许使用weak来引用实例变量的。这可能是出于防止野指针的出现。

iOS 在对象释放的方法deinit或者delloc中使用self调用懒加载方法的安全隐患。
众所周知,懒加载方法会在属性不存在时被初始化,而在iOS开发中,经常会在deinit或者dealloc 中移除一些kvo或者其他的释放工作,比如以下代码:

public lazy var tableView: UITableView = {
        let tableView = UITableView(frame: CGRect.zero, style: UITableView.Style.grouped)
        tableView.delegate = self
        tableView.dataSource = self
        tableView.separatorStyle = .none
        tableView.keyboardDismissMode = .onDrag
        return tableView
    }()
deinit {
        self.tableView.removeObserver(self, forKeyPath: "contentOffset")
    }

以上代码会在不经意间抛出一段可耐的异常:

objc[8413]: Cannot form weak reference to instance (0x103f186b0) of class

这是由于在对象正在释放的过程中,或者对象已经释放后,是不允许使用weak来引用实例变量的。这可能是出于防止野指针的出现。
如下图
截屏2020-02-22下午1.49.27.png

截屏2020-02-22下午1.48.59.png

从上图的线程调用栈,可以看出在控制器正在执行dealloc的时候,在deinit中初始化了tableView,并将此tableView的delegate设置为了当前控制器。我们知道delegate是通过weak引用的变量,那么此时相当于在这个控制器在释放时,又被别的对象作为weak引用。由于runtime底层在释放对象时会清理weak表,这是绝对不允许的。

runtime 是通过检查引用计数的个数来判断对象是否在 deallocting, 然后通过

    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));
    }

上面这段代码这段代码让程序crash。

再看一下 _objc_fatal 这个函数

void _objc_fatal(const char *fmt, ...)
{
    va_list ap; 
    char *buf1;
    char *buf2;

    va_start(ap,fmt); 
    vasprintf(&buf1, fmt, ap);
    va_end (ap);

    asprintf(&buf2, "objc[%d]: %s\n", getpid(), buf1);
    _objc_syslog(buf2);
    _objc_crashlog(buf2);

    _objc_trap();
}

可以看到这个函数实际会在控制台输出一段信息,然后调用 _bojc_trap() 引起 crash. 而最后一个函数调用刚好也对上我们之前的崩溃堆栈。

推荐阅读

目录