zoukankan      html  css  js  c++  java
  • 聊聊dealloc

    前言


    所有代码注释可在Objc-Runtime中查看

    iOS开发中,我们经常会通过dealloc来判断对象实例是否被释放,依据是当对象实例的引用计数变为0时,运行时会调用对象实例的dealloc方法,我们可以利用该方法做一些扫尾的工作。

    dealloc调用时机


    Objective-C的引用计数管理使用两种方式相结合,sidetableisa指针(指针并不是对象的真正内存地址,而是某些位用来进行了一些标志位的存放);接下来,我将以sidetable进行release来讨论dealloc的调用,直接上代码,如下sidetable_release(下文所有都会用sidetable_release来讨论)函数会在给对象发送release消息的时候调用,sidetable_release方法首先获取对象的引用计数,对引用计数相关标志位做操作,若对象实例可以被释放,将通过objc_msgSend发送SEL_dealloc消息,既调用对象的dealloc方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    uintptr_t
    objc_object::sidetable_release(bool performDealloc)
    {
    #if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
    #endif
    SideTable& table = SideTables()[this];
     
    bool do_dealloc = false;
     
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) {
    do_dealloc = true;
    table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
    } else if (it->second < SIDE_TABLE_DEALLOCATING) {
    // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
    do_dealloc = true;
    it->second |= SIDE_TABLE_DEALLOCATING;
    } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
    it->second -= SIDE_TABLE_RC_ONE;
    }
    table.unlock();
    // 进行释放操作,调用dealloc
    if (do_dealloc && performDealloc) {
    ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return do_dealloc;
    }

    dealloc方法的实现如下:

    1
    2
    3
    - (void)dealloc {
    _objc_rootDealloc(self);
    }

    直接调用_objc_rootDealloc方法来做处理,我们省略一些细节处理,通常情况下,dealloc方法最终会调用objc_dispose方法,内部又调用objc_destructInstance方法来进行析构操作,析构完成后将内存释放掉。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    id
    object_dispose(id obj)
    {
    if (!obj) return nil;
     
    objc_destructInstance(obj);
    // 做完各种析构操作后释放obj的内存
    free(obj);
     
    return nil;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    void *objc_destructInstance(id obj)
    {
    if (obj) {
    // Read all of the flags at once for performance.
    bool cxx = obj->hasCxxDtor();
    bool assoc = obj->hasAssociatedObjects();
     
    // This order is important.
    if (cxx) object_cxxDestruct(obj); // 调用C++析构器
    if (assoc) _object_remove_assocations(obj); // 移除对象相关的关联引用
    obj->clearDeallocating(); // 进行ARC相关操作,如weak置nil,清理计数位
    }
     
    return obj;
    }

    并发赋值


    考虑如下代码,我们来模拟并发的对变量obj进行赋值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    __block NSObject *obj = [NSObject new];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    while (YES) {
    obj = [NSObject new];
    }
    });
     
    while (YES) {
    obj = [NSObject new];
    }

    执行如上代码,我们发现,很快程序就会崩溃,异常为EXC_BAD_ACCESS,既访问已释放的内存地址,异常栈如下,在调用objc_msgSend发送SEL_dealloc方法时异常,而该方法正是在如上的objc_object::sidetable_release中被调用的,也就是release方法调用过程中。最终的原因就是对已释放的对象实例再次进行release操作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    0x106463a00 <+156>: callq 0x1064653e8 ; objc::DenseMapBase<objc::DenseMap<DisguisedPtr<objc_object>, unsigned long, true, objc::DenseMapInfo<DisguisedPtr<objc_object> > >, DisguisedPtr<objc_object>, unsigned long, objc::DenseMapInfo<DisguisedPtr<objc_object> >, true>::FindAndConstruct(DisguisedPtr<objc_object> const&)
    0x106463a05 <+161>: movq $0x2, 0x8(%rax)
    0x106463a0d <+169>: movl -0x2c(%rbp), %ebx
    0x106463a10 <+172>: movq %r15, %rdi
    0x106463a13 <+175>: callq 0x1064669fa ; symbol stub for: os_unfair_lock_unlock
    0x106463a18 <+180>: testb %bl, %bl
    0x106463a1a <+182>: je 0x106463a2e ; <+202>
    0x106463a1c <+184>: leaq 0x55a8ad(%rip), %rax ; SEL_dealloc
    0x106463a23 <+191>: movq (%rax), %rsi // 在这访问了已释放的内存地址
    0x106463a26 <+194>: movq %r14, %rdi
    0x106463a29 <+197>: callq 0x106465940 ; objc_msgSend
    0x106463a2e <+202>: movl $0x1, %eax
    0x106463a33 <+207>: jmp 0x106463a4c ; <+232>

    为什么会导致这样的结果呢?原因其实是,对属性的赋值操作并不是原子操作,对属性的赋值其实是调用属性的setter方法,默认setter代码实现如下:

    1
    2
    3
    4
    5
    6
    7
    - (void)setObj:(NSObject *)obj {
    if (obj != _obj) { // 1
    id oldValue = _obj; // 2
    _obj = [obj retain]; // 3
    [oldValue release]; // 4
    }
    }

    我们考虑两个线程同时进行setObj:赋值操作,当走到第4步时,两个线程同时尝试进行release操作,结果是一个线程成功的释放对象,而另一个线程会在release函数调用过程中访问已经释放的内存区域,这就导致了崩溃。

    dealloc在哪个线程被调用


    dealloc并不总是在主线程中被调用,从如上sidetable_release方法,我们可得知,其调用线程为最后一个调用release方法的线程,当需要释放对象时,向对象实例发送SEL_dealloc(即dealloc)消息。

    也就是说,dealloc方法有可能在任何线程被调用,这就需要注意一点,就是在dealloc中进行UIKit相关API的操作(UIKit相关API只能在主线程操作)。

    参考


    1. https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmPractical.html#//apple_ref/doc/uid/TP40004447-SW13
    ------------------越是喧嚣的世界,越需要宁静的思考------------------ 合抱之木,生于毫末;九层之台,起于垒土;千里之行,始于足下。 积土成山,风雨兴焉;积水成渊,蛟龙生焉;积善成德,而神明自得,圣心备焉。故不积跬步,无以至千里;不积小流,无以成江海。骐骥一跃,不能十步;驽马十驾,功在不舍。锲而舍之,朽木不折;锲而不舍,金石可镂。蚓无爪牙之利,筋骨之强,上食埃土,下饮黄泉,用心一也。蟹六跪而二螯,非蛇鳝之穴无可寄托者,用心躁也。
  • 相关阅读:
    性能测试(硬件性能指标汇总)
    jmeter中通过beanshell访问eclipse中导出jar中的java类的方法
    Linux性能优化参考
    单机到分布式集群简介
    java代码(生成日历时间)
    explian使用介绍
    java代码(处理json串)
    java代码(ascii与字母互转)
    jmeter分布式测试配置
    性能测试整体解决方案技术架构图、模型体系图、LR性能测试流程图
  • 原文地址:https://www.cnblogs.com/feng9exe/p/14396155.html
Copyright © 2011-2022 走看看