iOS:对象释放流程

参考文档:

对象生命周期由引用计数器的概念管理,每次对象失去一个被引用关系,会调用对应的release操作做引用计数器减1操作,当对象的引用计数为0时,会走真正的对象释放流程,走到dealloc释放方法;

引用计数器-对象调用release

objc_object::release()
{
    ASSERT(!isTaggedPointer());

    if (fastpath(!ISA()->hasCustomRR())) {
        sidetable_release();
        return;
    }

    ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release));
}
bjc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];

    bool do_dealloc = false;
    //加锁
    table.lock();
//获取当前对象所在的sidetable(一个hash表),在sidetable.refcnts(RefcountMap,一个map)中查到当前对象的迭代器
    RefcountMap::iterator it = table.refcnts.find(this);
    //接着判断迭代器是否是指向了sidetable的end
    //如果是就代表找不到:
    if (it == table.refcnts.end()) {
      //将对象标记为“正在析构”
      //标记需要dealloc
        do_dealloc = true;
        table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
    } else if (it->second < SIDE_TABLE_DEALLOCATING) {
      //判断之前存储的引用计数值是否为 0,避免负数
      //将对象标记为“正在析构”
      //标记需要dealloc
        // 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();
    if (do_dealloc  &&  performDealloc) {
    //调用对象的dealloc方法:
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return do_dealloc;
}

dealloc调用流程

NSObject的dealloc会调用 _objc_rootDealloc(self);

- (void)dealloc {
    _objc_rootDealloc(self);
}
_objc_rootDealloc(id obj)
{
    //是否还活着 断言
    assert(obj);
   //调用 objc_object::rootDealloc()
    obj->rootDealloc();
}
objc_object::rootDealloc()
{
    //是否使用TaggedPointer优化 是直接返回
    if (isTaggedPointer()) return;  // fixme necessary?

   //不需要处理object_dispose的所有内容
    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
       //调用object_dispose
        object_dispose((id)this);
    }
}
id object_dispose(id obj)
{
    //是否为nil 直接返回
    if (!obj) return nil;
    //调用 void *objc_destructInstance(id obj)
    objc_destructInstance(obj);    
    free(obj);
    return nil;
}
void *objc_destructInstance(id obj) 
{
//如果不为nil 才处理
    if (obj) {
        //是否有析构函数  这个bool值取决于当前类以及父类往上是否有实例变量,如果有实例变量当前类就有.cxxDestruct,当前类或父类有此方法值=YES,都没有才=NO
        bool cxx = obj->hasCxxDtor();
       //是否有关联对象
        bool assoc = obj->hasAssociatedObjects();
        // This order is important.
      //如果有析构函数 调用 void object_cxxDestruct(id obj)
        if (cxx) object_cxxDestruct(obj);
       //如果有关联对象 移除关联对象
        if (assoc) _object_remove_assocations(obj);
        //调用objc_clear_deallocating()清空引用计数表
        obj->clearDeallocating();
    }
    return obj;
}
析构函数
void object_cxxDestruct(id obj)
{
   //如果为nil 直接retun
    if (!obj) return;
   //是否使用TaggedPointer优化 是直接return
    if (obj->isTaggedPointer()) return;
   //调用 static void object_cxxDestructFromClass(id obj, Class cls)
    object_cxxDestructFromClass(obj, obj->ISA());
}
static void object_cxxDestructFromClass(id obj, Class cls)
{
    void (*dtor)(id);

    // Call cls's dtor first, then superclasses's dtors.

    //往父类递归调用.cxxDestruct 直到hasCxxDtor=NO return结束
    for ( ; cls; cls = cls->superclass) {
        if (!cls->hasCxxDtor()) return; 
        // 在类和缓存中查找方法
        dtor = (void(*)(id))
            lookupMethodInClassAndLoadCache(cls, SEL_cxx_destruct);
        // 查找到cxx_destruct方法(SEL_cxx_destruct = sel_registerNameNoLock(".cxx_destruct", NO);)
        if (dtor != (void(*)(id))_objc_msgForward_impcache) {
            if (PrintCxxCtors) {
                _objc_inform("CXX: calling C++ destructors for class %s", 
                             cls->nameForLogging());
            }
            (*dtor)(obj);
        }
    }
}

.cxx_destruct方法原本是为了C++对象析构的,ARC借用了这个方法插入代码实现了自动内存释放的工作

关联对象
void objc_removeAssociatedObjects(id object) 
{
    if (object && object->hasAssociatedObjects()) {
        _object_remove_assocations(object);
    }
}

关联对象由一个单独单独的管理类进行统一管理,维护有一个哈希表AssociationsHashMap,key为obj,value为一个哈希表ObjectAssociationMap,存储着关联的属性:值ObjcAssociation;可以理解为两层哈希表;
{
obj:{
name:value,
age:value
}
}

void _object_remove_assocations(id object) {
    vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        if (associations.size() == 0) return;
        disguised_ptr_t disguised_object = DISGUISE(object);
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) {
            // copy all of the associations that need to be removed.
            ObjectAssociationMap *refs = i->second;
            for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
                elements.push_back(j->second);
            }
            // remove the secondary table.
            delete refs;
            associations.erase(i);
        }
    }
    // the calls to releaseValue() happen outside of the lock.
    for_each(elements.begin(), elements.end(), ReleaseValue());
}
struct ReleaseValue {
    void operator() (ObjcAssociation &association) {
        releaseValue(association.value(), association.policy());
    }
};
static void releaseValue(id value, uintptr_t policy) {
    if (policy & OBJC_ASSOCIATION_SETTER_RETAIN) {
        return objc_release(value);
    }
}
清空引用计数表
void 
objc_clear_deallocating(id obj) 
{
    assert(obj);

    if (obj->isTaggedPointer()) return;
    obj->clearDeallocating();
}
objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        // Slow path for raw pointer isa.
       //调用sidetable_clearDeallocating()把对象的weak指针置nil,把对象的计数引用移除
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        /判断是否有过弱引用   是否因为计数太大有多个sidetable
        // Slow path for non-pointer isa with weak refs and/or side table data.
       //调用clearDeallocating_slow();内部再分开判断各自实现sidetable_clearDeallocating的内容
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}
objc_object::sidetable_clearDeallocating()
{
    SideTable& table = SideTables()[this];

    // clear any weak table items
    // clear extra retain count and deallocating bit
    // (fixme warn or abort if extra retain count == 0 ?)
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
            // weak指针置nil,
            weak_clear_no_lock(&table.weak_table, (id)this);
        }
        // 把对象的计数引用移除
        table.refcnts.erase(it);
    }
    table.unlock();
}


部分关键点注解

Tagged Pointer介绍

从64bit开始,iOS引入Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象存储。指针并没有指向真正的内容存储在指针中;这类对象没有引用计数器内存管理流程。

#   define _OBJC_TAG_MASK (1UL<<63)
_objc_isTaggedPointer(const void * _Nullable ptr)
{
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}

nonpointer介绍

在支持nonpointer的对象中引用计数会又选存储在extra_rc中,isa位域extra_rc溢出,则会选择将将引用计数的RC_HALF(如果extra_rc占据8bit,则RC_HALF=2^7)保存在isa中,另一半RC_HALF叠加保存在sidetable中.之所以这样选择是因为isa_t的存取理论上会比sidetable的操作效率上快很多,这样做既可以使超出extra_rc存储范围的引用计数得到有效存储,又可以确保引用计数的增减足够快速(存取都以extra_rc优先)。

1、在开启nonpointer的对象中,对象的引用计数包括两部分:
1)存储在isa中的引用计数(isa.extra_rc);
2)存储在sidetable中的引用计数.
2、在未开启nonpointer的对象中,对象的引用计数全部存储在sidetable中,只需要从sidetable中获取就可以.

isa_t结构(arm64)


# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : 6;                                       \
      uintptr_t weakly_referenced : 1;                                       \
      uintptr_t deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)

fastpath、slowpath

#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))

__builtin_expect指令的写法为:__builtin_expect(EXP, N)。
意思是:EXP==N的概率很大。

__builtin_expect() 是 GCC (version >= 2.96)提供给程序员使用的,目的是将“分支转移”的信息提供给编译器,这样编译器可以对代码进行优化,以减少指令跳转带来的性能下降。
__builtin_expect((x),1)表示 x 的值为真的可能性更大;
__builtin_expect((x),0)表示 x 的值为假的可能性更大。
也就是说,使用fastpath(),执行 if 后面的语句的机会更大,使用 slowpath(),执行 else 后面的语句的机会更大。通过这种方式,编译器在编译过程中,会将可能性更大的代码紧跟着前面的代码,从而减少指令跳转带来的性能上的下降。

.cxx_desctruct

参考链接:

ARC下对象的成员变量于编译器插入的.cxx_desctruct方法自动释放;
ARC下[super dealloc]方法也由编译器自动插入;

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容