iOS—strong、weak的实现以及dealloc的调用流程

news/2024/7/20 21:58:23 标签: ios, objective-c

文章目录

    • __strong修饰符
    • ARC在编译器、运行期做了哪些工作?
    • __weak修饰符
      • __weak在使用过程中调用的函数
      • objc_initWeak
      • storeWeak
      • SideTable
        • weak_table_t 结构
        • weak_entry_t 结构
      • weak_register_no_lock方法,添加弱引用
        • weak_entry_for_referent ,获取对象对应的weak_entry
        • append_referrer,向对象对应的weak_entry中添加弱引用指针的地址
      • weak_unregister_no_lock,移除弱引用
    • dealloc调用流程
        • _objc_rootDealloc
        • object_dispose
        • objc_destructInstance
        • clearDeallocating
        • clearDeallocating_slow
        • weak_clear_no_lock
      • objc_loadWeakRetained
      • objc_destroyWeak
    • dealloc执行流程总结
    • weak总结

__strong修饰符

在这里插入图片描述

命令行使用下面的命令来将 Objective-C 代码转成 LLVM 中间码
clang -S -fobjc-arc -emit-llvm main.m -o main.ll
LLVM中间码如下:

define i32 @main(i32 %0, i8** %1) #0 {
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  %5 = alloca i8**, align 8
  %6 = alloca i8*, align 8
  %7 = alloca i8*, align 8
  store i32 0, i32* %3, align 4
  store i32 %0, i32* %4, align 4
  store i8** %1, i8*** %5, align 8
  %8 = call i8* @llvm.objc.autoreleasePoolPush() #1
  %9 = load %struct._class_t*, %struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_", align 8
  %10 = bitcast %struct._class_t* %9 to i8*
  
  %11 = call i8* @objc_opt_new(i8* %10)
  
  %12 = bitcast i8* %11 to %0*
  %13 = bitcast %0* %12 to i8*
  store i8* %13, i8** %6, align 8
  %14 = load i8*, i8** %6, align 8
  
  %15 = call i8* @llvm.objc.retain(i8* %14) #1
  
  store i8* %15, i8** %7, align 8
  
  call void @llvm.objc.storeStrong(i8** %7, i8* null) #1
  call void @llvm.objc.storeStrong(i8** %6, i8* null) #1
  
  call void @llvm.objc.autoreleasePoolPop(i8* %8)
  ret i32 0
}

简化如下:

  %11 = call i8* @objc_opt_new(i8* %10)        //创建对象
  %15 = call i8* @llvm.objc.retain(i8* %14) #1  //引用计数+1
  call void @llvm.objc.storeStrong(i8** %7, i8* null) #1  //release
  call void @llvm.objc.storeStrong(i8** %6, i8* null) #1  //release

对于storeStrong,查看源码实现如下:
在这里插入图片描述
location:指针的地址
obj:指向对象的指针,存储对象的地址
逻辑可以理解为

  • 检查参数obj与指针*location存储的地址是否相同
  • 若相同,直接返回
  • 若不同
    • 对新的对象obj引用计数+1,当前 *location指向obj
    • 对原来指向的对象引用计数-1

简化后的中间码storeStrong第二个参数为null,就相当于是对对象进行了release操作。

ARC在编译器、运行期做了哪些工作?

编译期:根据代码执行的上下文语境,在适当的位置插入retain,release

运行期:

  • __weak修饰的变量能够在其指向的对象引用计数为0时,自动设置为nil。
  • ARC可以在运行期检测出autorelease及紧跟其后的retain,此时不直接掉用autorelease方法,而是改为调用objc_autoreleaseReturnValue函数。这个函数会检查当前方法返回之后的即将要执行的代码。如果在返回的对象上执行retain操作,则设置全局数据结构中的一个标志位,而不执行autorelease操作。
    与之相似,如果方法返回了一个自动释放的对象,此时不直接执行retain,而是改为执行objc_retainAutoreleaseReturnValue函数,这个函数会检测刚提到的标志位,如果已经置位,则不执行retain。
    设置并检测标志位,要比调用autorelease和retain快。

内存管理语义在方法名中表现出来
alloc、new、copy、mutableCopy开头的方法,其返回的对象归调用者所有
类似于array这种非上述四种命名方式并返回对象的方法,其返回的对象并不归调用者所有,会调用autorelease方法,以确保可以多存活一段时间。
若紧跟其后会对该对象进行retain操作,那么这一对操作就是多余的,为了提高性能,可将二者删去,但又考虑到需要兼容不实用ARC的代码,所以上述的优化保证了向后兼容性


__weak修饰符

__weak在使用过程中调用的函数

在这里插入图片描述
汇编语言如下
在这里插入图片描述
实际调用如下
objc_alloc_init
objc_initWeak
objc_release
objc_destroyWeak

首先创建并初始化对象,通过objc_initWeak函数初始化附有__weak修饰符的变量,由于弱引用,该指针变量不持有对象,所以对象会立即释放,执行objc_release,最后调用objc_destroyWeak释放附有__weak修饰符的变量。


在这里插入图片描述
汇编:
在这里插入图片描述
实际调用:
objc_alloc_init
objc_initWeak
objc_destroyWeak
objc_storeStrong

与上述过程差不太多


在这里插入图片描述
汇编:
在这里插入图片描述
实际调用:
objc_alloc_init
objc_initWeak
objc_loadWeakRetained
NSLog
objc_release
objc_destroyWeak
objc_storeStrong

与之前不同的是,在打印__weak修饰的obj1前,调用了objc_loadWeakRetained,打印完后调用了objc_release。


objc_initWeak

objc_initWeak源码如下

id objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

该方法有两个参数location、newObj

  • location:存储__weak指针的地址(即指针的指针),可通过*location对__weak指针进行修改赋值
  • newObj:所引用的对象

通过源码可知,所引用的对象为nil则直接返回nil
真正的实现是storeWeak

storeWeak实现如下

storeWeak


// Template parameters.

enum HaveOld { DontHaveOld = false, DoHaveOld = true };  //是否有旧值
enum HaveNew { DontHaveNew = false, DoHaveNew = true };  //是否有新值

//操作正在释放中的对象是否会crash
enum CrashIfDeallocating {
    DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};

template <HaveOld haveOld, HaveNew haveNew,
          enum CrashIfDeallocating crashIfDeallocating>
          
static id 
storeWeak(id *location, objc_object *newObj)
{
    ASSERT(haveOld  ||  haveNew);
    if (!haveNew) ASSERT(newObj == nil);

    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;

    // Acquire locks for old and new values.
    // Order by lock address to prevent lock ordering problems. 
    // Retry if the old value changes underneath us.
 retry:
 
    if (haveOld) {   
    	//如果__weak指针之前弱引用过一个obj,则将这个obj对应的SideTable取出,赋值给oldTable
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {         
    	//如果__weak指针之前没有弱引用过一个obj,则oldTable = nil
        oldTable = nil;
    }
    
    if (haveNew) {   //如果__weak指针要弱引用一个新的obj,则将obj对应的sideTable从sidetables中取出,赋值给newTable
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;  //如果__weak指针不需要引用一个新obj,则newtbale为nil
    }

	//加锁操作,防止多线程中竞争冲突
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

	//location应该与oldObj保持一致,如果不同,说明当前location已经处理过oldObj可是又被其他线程所修改
    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }


    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no 
    // weakly-referenced object has an un-+initialized isa.
    //如果有新值
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized())    //如果cls还没有初始化,先初始化,再尝试设置weak
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            class_initialize(cls, (id)newObj);

            // If this class is finished with +initialize then we're good.
            // If this class is still running +initialize on this thread 
            // (i.e. +initialize called storeWeak on an instance of itself)
            // then we may proceed but it will appear initializing and 
            // not yet initialized to the check above.
            // Instead set previouslyInitializedClass to recognize it on retry.
            previouslyInitializedClass = cls;      //记录一下perviouslyInitializedClass,防止该if再次进入

            goto retry;   //重新获取一遍newObj,这时newObj应该已经初始化过了
        }
    }

    // Clean up old value, if any.
    if (haveOld) {
		//如果__weak指针之前弱引用过别的对象oldObj,则调用weak_unregister_no_lock,在oldObj所在的weak_entry_t中移除该weak_ptr地址
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    //如果__weak指针需要弱引用新的对象newObj
    if (haveNew) {
		//1.调用weak_register_no_lock方法,将__weak指针的地址记录到newObj所在的weak_entry_t 中的存储着 弱引用指针的地址 的数组中
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

		//2.更新newObj的isa的weakly_referenced bit标志位
		//表示newObj是有弱引用变量,释放时要去清空弱引用表
        // Set is-weakly-referenced bit in refcount table.
        if (!newObj->isTaggedPointerOrNil()) {
            newObj->setWeaklyReferenced_nolock();
        }

		
        // Do not set *location anywhere else. That would introduce a race.
        //3.给*location赋值,也就是__weak指针指向了newObj。这里并没有增加newObj的引用计数
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
    
    //解锁,其他线程可以访问oldTable,newTable了
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    // This must be called without the locks held, as it can invoke
    // arbitrary code. In particular, even if _setWeaklyReferenced
    // is not implemented, resolveInstanceMethod: may be, and may
    // call back into the weak reference machinery.
    callSetWeaklyReferenced((id)newObj);

	//返回newObj,此时的newObj与刚传进来时相比,weakly-referenced bit位 置1
    return (id)newObj;
}

实现逻辑:

  • haveOld、haveNew,以模板的方式传入的,是bool类型的参数。 分别表示weak指针之前是否弱引用一个对象,weak指针是否需要弱引用新的对象
  • 从SideTables中根据旧对象和新对象的地址分别取出对应的(SideTable结构)oldTable、newTable
  • 如果weak指针之前指向过一个对象,则会调用weak_unregister_no_lock,通过旧对象的地址 从 oldTable中的weak_table的weak_entries数组中 找到对应的weak_entry_t类型的元素,在该元素中存储 弱引用指针地址 的数组中 删除weak指针的地址。
    可能看着这个过程有点晕,下面会详细分析,可以忽略上面这三行
    总之,通过调用weak_unregister_no_lock 方法从存储着 弱引用旧对象指针的地址 的数组中删除当前weak指针的地址
  • 如果weak指针需要指向一个新对象,则会调用weak_register_no_lock方法 将weak指针的地址添加到存储着 弱引用新对象指针的地址 的数组中
  • 调用setWeaklyReferenced_nolock方法修改weak指针新引用的对象的isa的bit标志位

SideTable

SideTable的定义

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;    //存引用计数 散列表结构
    weak_table_t weak_table;
}

有三个成员

  • spinlock_t slock:自旋锁,用于上锁、解锁SideTable
  • RefcountMap refcnts:用于存储OC对象的引用计数的hash表(仅在未开启isa优化或在isa优化情况下extra_rc溢出情况下才会存储到这里)
  • weak_table_t weak_table:存储对象的弱引用指针的hash表。

。。。后续继续补充

weak_table_t 结构

在这里插入图片描述

  • weak_entries: hash数组,数组元素为weak_entry_t,即hash表的节点类型是weak_entry_t。
    weak_entries是以对象的地址作为key,以存储着 弱引用对象的指针的地址的 数组 作为value的hash表
  • num_entries: hash数组中的元素个数
  • mask: weak_entries数组长度-1,会参与hash计算,注意是hash数组的长度,而不是元素个数
  • max_hash_displacement: 可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误(hash表中的冲突次数绝不会超过改值)

weak_entry_t 结构

weak_entry_t的结构中有一个数组,存储的元素的弱引用对象指针的地址(指针的指针),通过操作指针的指针就可以使元素的弱引用对象指针在对象析构后指向nil

#define WEAK_INLINE_COUNT 4
#define REFERRERS_OUT_OF_LINE 2

struct weak_entry_t {
    DisguisedPtr<objc_object> referent;    //被弱引用的对象
	
	//引用个数少于4,用inline_referrers[WEAK_INLINE_COUNT]数组
	//引用个数大于4,用weak_referrer_t *referrers,动态数组
    union {
        struct {
            weak_referrer_t *referrers;              //存储着弱引用对象的指针的地址的动态数组
            uintptr_t        out_of_line_ness : 2;   //是否使用动态数组的标记位
            uintptr_t        num_refs : PTR_MINUS_2; //动态数组中的元素个数
            uintptr_t        mask;  				 //动态数组数组长度-1
            uintptr_t        max_hash_displacement;  //可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误,hash表中的冲突次数决不会超过该值
        };
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };

	//用来判断采取哪种数组存储
    bool out_of_line() {
        return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
    }

    weak_entry_t& operator=(const weak_entry_t& other) {
        memcpy(this, &other, sizeof(other));
        return *this;
    }

//构造方法,初始化了静态数组inline_referrers
    weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
        : referent(newReferent)
    {
        inline_referrers[0] = newReferrer;
        for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
            inline_referrers[i] = nil;
        }
    }
};

在weak_entry_t的结构定义中,我们可以看到一个共用体,共用体中有一个定长数组和动态数组,通过这两种方式来存储弱引用对象的指针的地址,通过out_of_line()来判断采取哪种存储方式。当弱引用对象的指针数目小于WEAK_INLINE_COUNT时,使用定长数组。当超过WEAK_INLINE_COUNT时,将定长数组中的元素转移到动态数组中,并之后都使用动态数组存储。

  • weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; 定长数组
    #define WEAK_INLINE_COUNT 4
  • weak_referrer_t *referrers; 动态数组

weak_table(弱引用表)结构中第一个元素weak_entries可以看做是一个hash结构的表,Key是对象的地址,Value是存储着弱引用对象指针的地址的数组

这里放一张SideTables完整的结构图,先主要看一下weak_table的结构
weal_table是weak_table_t结构体类型,有四个成员, 主要看weak_entries,其他

  • weak_entries,是一个数组,元素是weak_entry_t类型
    从weak_entry_t结构的定义中可以看到,有两个成员:
    • referent,被弱引用的对象
    • referrers,数组,数组元素为 弱引用对象的指针的地址(指针的指针)
  • num_entries
  • mask
  • max_hash_displacement

在这里插入图片描述

weak_register_no_lock方法,添加弱引用


enum WeakRegisterDeallocatingOptions {
    ReturnNilIfDeallocating,
    CrashIfDeallocating,
    DontCheckDeallocating
};

//参数
//weak_table:weak_table_t 结构类型的全局的弱引用表
//referent_id:weak指针
//*referrer_id:weak指针的地址
//
id weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
{
	//referent存储对象的地址 referent指向对象
    objc_object *referent = (objc_object *)referent_id;
    //referrer存储弱引用对象指针的地址
    objc_object **referrer = (objc_object **)referrer_id;

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

	//确保被引用的对象可用
    // ensure that the referenced object is viable
    if (deallocatingOptions == ReturnNilIfDeallocating ||
        deallocatingOptions == CrashIfDeallocating) {
        bool deallocating;
        if (!referent->ISA()->hasCustomRR()) {
            deallocating = referent->rootIsDeallocating();
        }
        else {
            // Use lookUpImpOrForward so we can avoid the assert in
            // class_getInstanceMethod, since we intentionally make this
            // callout with the lock held.
            auto allowsWeakReference = (BOOL(*)(objc_object *, SEL))
            lookUpImpOrForwardTryCache((id)referent, @selector(allowsWeakReference),
                                       referent->getIsa());
            if ((IMP)allowsWeakReference == _objc_msgForward) {
                return nil;
            }
            deallocating =
            ! (*allowsWeakReference)(referent, @selector(allowsWeakReference));
        }

		//正在析构的对象不能被弱引用
        if (deallocating) {
            if (deallocatingOptions == 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_table中找到referent(对象)对应的weak_entry,并将referrer(弱引用指针的地址)加入到weak_entry中
    weak_entry_t *entry;
    //如果能找到weak_entry,则将referrer(弱引用对象的指针的地址)插入到weak_entry中
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        //将referrer(弱引用对象的指针的地址)插入到entry的存储着 弱引用对象的指针的地址 数组中
        append_referrer(entry, referrer);
    } 
    else { //如果找不到referent对应的entry,就新建一个entry
        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;
}
  • 如果referent采用了TaggedPointer计数方式,直接返回,不做任何操作
  • 如果对象可用,且可以被weak引用,则调用 weak_entry_for_referent方法根据弱引用对象的地址从弱引用计数表中找到对应的weak_entry,如果能够找到对应的weak_entry,则调用append_referrer方法向该弱引用对象对应的weak_entry中的数组插入weak指针的地址;否则新建一个weak_entry

weak_entry_for_referent ,获取对象对应的weak_entry

通过对象的地址从weak_table的weak_entries数组中取出相应的weak_entry(存储着对象、数组,数组元素为弱引用该对象的指针们的地址)

static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
    ASSERT(referent);

    weak_entry_t *weak_entries = weak_table->weak_entries;

    if (!weak_entries) return nil;

    //mask实际值是表大小的size-1,而size是2的n次方进行扩张,所以mask的所有位都为1
    //hash_pointer也是使用指针地址,映射到一个索引
    // 这里通过 索引& weak_table->mask的位操作,来确保index不会越界,范围在[0,size]
    size_t begin = hash_pointer(referent) & weak_table->mask;
    size_t index = begin;
    size_t hash_displacement = 0;
    
    while (weak_table->weak_entries[index].referent != referent) {
        index = (index+1) & weak_table->mask;
        
        // 触发bad weak table crash
        if (index == begin) bad_weak_table(weak_table->weak_entries);
        hash_displacement++;
        
        //当hash冲突超过了可能的max hash 冲突时,说明元素没有在hash表中,返回nil
        if (hash_displacement > weak_table->max_hash_displacement) {
            return nil;
        }
    }
    
    //返回对象referent对应的weak_entry
    return &weak_table->weak_entries[index];
}

append_referrer,向对象对应的weak_entry中添加弱引用指针的地址

//参数1 entry: 当前被弱引用的对象对应的weak_entry,结构中存储着当前被弱引用的对象、元素为弱引用对象的指针的地址 的数组
//参数2 new_referrer: 将要弱引用对象的指针的地址
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
	//如果weak_entry未使用动态数组
    if (! entry->out_of_line()) {
        // Try to insert inline.
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == nil) {
            	//将弱引用对象的指针的地址存进去 
                entry->inline_referrers[i] = new_referrer;
                return;
            }
        }

		//如果定长数组已满,要转为动态数组referrers
        // Couldn't insert inline. Allocate out of line.
        weak_referrer_t *new_referrers = (weak_referrer_t *)
            calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
        // This constructed table is invalid, but grow_refs_and_insert
        // will fix it and rehash it.
        
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            new_referrers[i] = entry->inline_referrers[i];
        }
        entry->referrers = new_referrers;
        entry->num_refs = WEAK_INLINE_COUNT;            //动态数组元素个数
        entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;//是否使用动态数组的标记位
        entry->mask = WEAK_INLINE_COUNT-1;				
        //可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误,hash表中的冲突次数决不会超过该值
        entry->max_hash_displacement = 0;
    }

	//对动态数组的附加处理
    ASSERT(entry->out_of_line());  //使用的一定是动态数组

	//如果动态数组元素个数大于等于数组总空间的3/4,则扩展数组空间为当前长度的一倍
    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
    	//扩容并插入
        return grow_refs_and_insert(entry, new_referrer);
    }
    
    //如果不需要扩容,直接插入到weak_entry中
    size_t begin = w_hash_pointer(new_referrer) & (entry->mask); 
    size_t index = begin; //初始的hash index
    size_t hash_displacement = 0; //记录hash冲突的次数,也就是hash再位移的次数
    while (entry->referrers[index] != nil) {
        hash_displacement++;
        //index+1,移到下一个位置,& entry->mask是为了确保index在数组的[0, size)范围内
        index = (index+1) & entry->mask; 
        //index == begin,意味着数组绕了一圈都没有找到合适位置,这时候一定是出了什么问题。
        if (index == begin) bad_weak_table(entry);
    }
    //记录最大的hash冲突次数,max_hash_displacement意味着: 我们尝试至多max_hash_displacement次,肯定能够找到object对应的hash位置
    if (hash_displacement > entry->max_hash_displacement) {
        entry->max_hash_displacement = hash_displacement;
    }
	
	//将new_referrer(弱引用对象的指针的地址)存入数组中
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    //更新entry中动态数组的元素个数
    entry->num_refs++;
}
  • 首先判断是否使用定长数组,如果使用定长数组,将弱引用对象的指针的地址存入定长数组中
  • 如果定长数组已满,则转为动态数组,将定长数组中的元素转存到动态数组中,同时设置动态数组的一些值(数组元素个数等)
  • 如果动态数组中元素个数大于等于动态数组size的3/4,则扩容,扩为当前数组长度的一倍,同时将 弱引用对象的指针的地址存入
  • 不扩容则直接插入到动态数组中

weak_unregister_no_lock,移除弱引用

如果weak指针之前有指向旧对象,则会调用weak_unregister_no_lock方法将旧对象对应的entry中存储弱引用指针地址的数组中 移除weak指针的地址。

//参数1 weak_table: 旧对象对应的weak_table
//referent_id: 当前weak指针,(存储旧对象的地址)
//referrer_id: weak指针的地址
void
weak_unregister_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;

    weak_entry_t *entry;

	//weak指针为nil 直接返回
    if (!referent) return;

	//通过referent(旧对象的地址)从weak_table中的weak_entries数组中取出相应的entry
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
    	//将entry中的数组中的 referrer(weak指针的地址) 移除
        remove_referrer(entry, referrer);
	
		//移除元素后,要检查一下weak_entry的数组是否为空
        bool empty = true;
        if (entry->out_of_line()  &&  entry->num_refs != 0) {
            empty = false;
        }
        else {
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i]) {
                    empty = false; 
                    break;
                }
            }
        }

		//如果weak_entry中的hash数组为空,则需要将weak_entry从所在的weak_table中移除
        if (empty) {
            weak_entry_remove(weak_table, entry);
        }
    }

    // Do not set *referrer = nil. objc_storeWeak() requires that the 
    // value not change.
}

  • 通过旧对象的地址从weak_table中的weak_entries数组取出相应的entry
  • 从entry中的数组中移除weak指针的地址
  • 移除元素后,判断此时entry中的数组中是否还有元素(是否为空)
  • 数组为空,则将该entry从weak_table的weak_entires数组中移除

当对象释放时,weak指针又是怎样置nil的呢?

dealloc调用流程

_objc_rootDealloc

当对象的引用计数为0时,底层会调用_objc_rootDealloc方法对对象进行释放,_objc_rootDealloc方法又会去调用rootDealloc方法。
在这里插入图片描述
在这里插入图片描述
对于rootDealloc方法的实现,源码如下

inline void
objc_object::rootDealloc()
{
    //标签指针 直接返回
    if (isTaggedPointer()) return;  // fixme necessary?

    //如果isa是优化 且没有弱引用 没有关联对象 没有c++的析构函数 没有使用sideTable存引用计数,直接free
    if (fastpath(isa.nonpointer                     &&
                 !isa.weakly_referenced             &&
                 !isa.has_assoc                     &&
#if ISA_HAS_CXX_DTOR_BIT
                 !isa.has_cxx_dtor                  &&
#else
                 !isa.getClass(false)->hasCxxDtor() &&//这个不知道
#endif
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}
  • 首先判断是否是标签指针,如果是,直接返回
  • 如果对象的isa是优化了的isa、且对象没有被weak指针引用、没有关联对象、没有c++的析构函数、没有使用sideTable存引用计数,则直接free()
  • 如果不满足2中的条件,则会调用object_dispose

object_dispose

id 
object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}

  • object_dispose方法内部实现主要是调用了objc_destructInstance
  • 执行完objc_destructInstance,再free对象

objc_destructInstance

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++析构函数 则调用c++析构函数
        if (assoc) _object_remove_assocations(obj, /*deallocating*/true);  //移除关联对象
        obj->clearDeallocating();  
    }

    return obj;
}
  • 如果有自定义的C++析构方法,则调用C++析构函数。
  • 如果有关联对象,则移除关联对象并将其自身从Association Manager的map中移除
  • 用clearDeallocating方法清除对象的相关引用

clearDeallocating

inline void 
objc_object::clearDeallocating()
{
    //没有优化过的isa
    if (slowpath(!isa.nonpointer)) {
        // Slow path for raw pointer isa.
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // Slow path for non-pointer isa with weak refs and/or side table data.
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}

两个分支

  • 如果isa没有优化,则需要清理对象存储在SideTable中的引用计数。
  • 如果采用的是优化的isa
    判断是否有weak引用(isa.weakly_referenced为1,有弱引用) 或 使用SideTable的进行引用计数(isa.has_sidetable_rc为1,则使用了)
    • 符合上述情况至少一种,调用clearDeallocating_slow();

clearDeallocating_slow

objc_object::clearDeallocating_slow()
{
	//这里必定是优化的isa、有弱引用或使用SideTable进行引用计数
    ASSERT(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    //获取指针对应的sideTable
    SideTable& table = SideTables()[this];
    table.lock();
	
	//如果有弱引用
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }

	//如果采用SideTable进行引用计数
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);  //从SideTable的引用计数表中移除this
    }
    table.unlock();
}

这里主要看一下weak_clear_no_lock方法

weak_clear_no_lock

void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;

	//通过referent(对象的地址) 在weak_table中的weak_entries数组中取出相应的weak_entry
    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) {
        /// XXX shouldn't happen, but does with mismatched CF/objc
        //printf("XXX no entry for clear deallocating %p\n", referent);
        return;
    }

    // zero out references
    weak_referrer_t *referrers;
    size_t count;
    
    //获取数组地址、数组元素个数
    if (entry->out_of_line()) {
        referrers = entry->referrers;  
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    
    
    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i];   //取出每个weak指针的地址
        if (referrer) {
			
			//如果weak指针 引用了 referent, 则将weak指针置nil
            if (*referrer == referent) {
                *referrer = nil;
            }
			 
			 // 如果所存储的weak ptr没有weak 引用referent,这可能是由于runtime代码的逻辑错误引起的,报错
            else if (*referrer) {
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n", 
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }
    
    //将entry 从 weak_table中weak_entries数组中 移除
    weak_entry_remove(weak_table, entry);
}

objc_loadWeakRetained

最后讲一下当打印weak指针时,调用的函数
在我们打印弱引用指针的时候,会发现打印前会执行这个方法,打印完后会执行一个release
在这里插入图片描述

objc_loadWeakRetained,可以理解在使用弱引用变量之前,做了一次retain操作,以此确保使用时不会因为对象被释放导致出错,在用完之后立即释放。

//参数location 弱引用对象的指针的指针
id
objc_loadWeakRetained(id *location)
{
    id obj;
    id result;
    Class cls;

    SideTable *table;
    
 retry:
    // fixme std::atomic this load
    //obj指向对象
    obj = *location;
    //标签指针直接返回
    if (obj->isTaggedPointerOrNil()) return obj;
    
    //根据对象的地址从SideTables中获取对应的SideTable
    table = &SideTables()[obj];
    
    //如果被引用的对象在此期间发生变化则重试
    table->lock();
    if (*location != obj) {
        table->unlock();
        goto retry;
    }
    
    result = obj;

    cls = obj->ISA();
   
    if (! cls->hasCustomRR()) {
		 //类和超类没有自定义的retain/release/autorelease/retainCount/
//   _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference

        // Fast case. We know +initialize is complete because
        // default-RR can never be set before then.
        ASSERT(cls->isInitialized());
        //尝试retain
        if (! obj->rootTryRetain()) {
            result = nil;
        }
    }
    else {
		//是自定义
	
        // Slow case. We must check for +initialize and call it outside
        // the lock if necessary in order to avoid deadlocks.
        // Use lookUpImpOrForward so we can avoid the assert in
        // class_getInstanceMethod, since we intentionally make this
        // callout with the lock held.

		//如果类已将初始化或当前线程正在初始化类
        if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
			// 获取自定义 SEL_retainWeakReference 方法
            BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
                lookUpImpOrForwardTryCache(obj, @selector(retainWeakReference), cls);
            if ((IMP)tryRetain == _objc_msgForward) {
                result = nil;
            }
			//调用自定义函数
            else if (! (*tryRetain)(obj, @selector(retainWeakReference))) {
                result = nil;
            }
        }
        else {
			//类没有初始化, 则初始化类后回到retry重新执行
            table->unlock();
            class_initialize(cls, obj);
            goto retry;
        }
    }
        
    table->unlock();
    return result;
}
  • 通过弱引用指向的对象的地址,获取对应的SideTable,并上锁,防止在在此期间sideTable被修改
  • 判断是否包含自定义的retain
    • 没有,则使用默认的rootTryretain方法,使引用计数+1
    • 有自定义retain方法,则调用自定义方法,调用前先判断该对象所属的类是否初始化过,没有初始化就初始化再重新执行

objc_destroyWeak

可以看到objc_destroyWeak内部也是调用了storeweak,只不过参数2为nil,没有要引用的对象
在storeWeak中仅执行

void
objc_destroyWeak(id *location)
{
    (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
        (location, nil);
}

dealloc执行流程总结

  • 首先调用objc_rootDealloc()
  • 接下来调用rootDealloc,这时会判断是否可以直接进行释放操作,调用free()
    判断依据为:
    对象的isa是否为优化过的指针
    对象是否被弱引用过
    是否有关联对象
    是否有c++的析构函数
    是否使用SideTable进行引用计数
    • 如果以上都不满足,则可以进行释放操作,调用free()
    • 如果至少满足一点,将会调用object_dispose()
      object_dispose()内部实现是调用objc_destructInstance(),执行完这个函数,再调用free()
      • objc_destructInstance 实现流程如下:
        1. 判断是否有c++相关内容,有则调用相关的c++析构函数
        1. 判断是否有关联对象,有则移除关联对象
        1. 调用clearDeallocating(),
            • clearDeallocating()流程如下:
            1. 如果对象的isa是没有优化过的isa,调用sidetable_clearDeallocating(),清除对象对应的sidetable中的引用计数
            1. 如果对象的isa是优化过的isa,且有弱引用或sideTable中存储了引用计数,
              对于有弱引用,则从weak表中通废弃对象的地址获取对象对应的weak_entry,将weak_entry中存储的所有weak指针全部置nil,再从weak表中删除这个对象对应的weak_entry。(将指向该对象的weak指针全部置nil)
            1. 如果sideTable中存储了该对象的引用计数,则清除引用计数
  • Dealloc执行结束

weak总结

这里总结一下weak

  • 初始化一个weak指针指向对象的地址时,会调用objc_initWeak
  • objc_initWeak内部调用的是objc_storeWeak,第一个参数是weak指针的地址,第二个参数是要指向的对象的地址。
  • 对weak指针进行赋值也是调用的objc_storeWeak
  • objc_storeWeak的作用是解除weak指针对旧对象的弱引用,从weak表中旧对象对应的记录(weak_entry)中删除weak指针的地址;添加对新对象的弱引用,从weak表中找到新对象对应的记录(weak_entry),并将weak指针的地址存入,如果没有找到新对象对应的weak_entry,则新建一个并将weak指针地址存入
  • 当超过weak指针的作用域时,会调用objc_destroyWeak
  • objc_destroyWeak内部调用的其实也是objc_storeWeak,此时与给weak指针赋值不同的是第一个参数是weak指针的地址,而第二个参数是nil。所以仅是从weak表中找出当前对象对应的记录,然后删除weak指针的地址。
  • 当对象被废弃时,即当对象的引用计数为0时,则会调用dealloc函数,当该对象有弱引用或使用了sidetable进行引用计数时,则最终会走到objc_clear_deallocating函数
    • 如果该对象有弱引用,则从weak表中获取以该对象地址为键值的记录,通过该记录中存储的所有weak指针的地址,将所有weak指针置nil。然后从weak表中删除该记录。
      (具体过程:根据对象的地址从weak表中获取相应的entry,取出entry中的数组,这个数组存储的是所有弱引用该对象的指针的地址,遍历这个数组,将这些指针置nil,最后将这个对象对应的entry从weak表中删除)
    • 如果使用sideTable的引用计数表存储了该对象的引用计数,则删除以该对象地址作为键值的记录

对于weak表,其实是一个hash表,key对象的地址value是一个数组,这个数组存储的是所有弱引用该对象的指针的地址。

参考
参考


http://www.niftyadmin.cn/n/1617802.html

相关文章

iOS—KVO用法及本质

文章目录KVOKVO 的使用KVO本质总结一些问题KVO KVO全称KeyValueObserving&#xff0c;键值监听&#xff0c;是苹果提供的一套事件通知机制。允许对象监听另一个对象特定属性的改变&#xff0c;并在改变时接受到事件。 KVO 的使用 三个方法 注册观察者 addObserver:(nonnull…

iOS—KVC本质

文章目录KVC一些API集合运算符setValue:forKey: 原理&#xff0c;赋值过程ValueForKey&#xff1a;原理&#xff0c;获值过程一些问题未完未完未完 后续补充KVC KVC全称是Key-Value-Coding&#xff0c;键值编码&#xff0c;可以通过一个key来访问某个属性 一些API 常见的API…

iOS—GCD详解

文章目录多线程编程进程线程线程与进程的关系多线程编程多线程编程的问题GCD什么是GCDGCD任务和队列GCD的使用步骤队列的创建队列的获取任务的创建方法任务与队列不同组合方式的区别同步执行 并发队列异步执行 并发队列同步执行 串行队列异步执行 串行队列同步执行 主队列…

iOS—pthread、NSThread简单了解

文章目录pthreadpthread的使用pthread其他相关方法NSThread创建、启动线程线程相关用法线程状态控制方法线程之间的通信NSThread线程安全pthread pthread是一套通用的多线程API&#xff0c;可以在Unix/Linux/Windows等系统跨平台使用&#xff0c;使用C语言编写&#xff0c;需要…

iOS—NSOperation、NSOperationQueue简单了解

文章目录NSOperation、NSOperationQueue为什么要使用NSOperation、NSOperationQueueNSOperation、NSOperationQueue操作和操作队列NSOperation、NSOperationQueue使用步骤NSOperation、NSOperation基本使用创建操作使用子类NSInvocationOperation使用子类NSBlockOperation使用继…

iOS—单例模式

单例模式 保证一个类只有一个实例&#xff0c;并且提供一个全局的访问入口 系统为我们提供了哪些单例类 UIApplication(应用程序实例类) NSNotificationCenter(消息中心类) NSFileManager(文件管理类) NSUserDefaults(应用程序设置) NSURLCache(请求缓存类) NSHTTPCookieSto…

iOS—持久化的几种方案

文章目录数据持久化数据持有化方式分类内存缓存磁盘缓存沙盒持久化方式数据持久化 iOS中的永久存储&#xff0c;也就是在关机重新启动设备&#xff0c;或关闭应用时不会丢失数据。在实际开发中&#xff0c;往往需要持有存储数据的。 数据持有化的目的 快速展示&#xff0c;提…

iOS—NSURLSession简单使用

文章目录NSURLSession使用NSURLSession的获取NSURLSessionTask创建Task使用NSURLSession发送GET请求block方式创建task代理方法发送POST请求NSURLSession&#xff0c;苹果对它的定位是作为NSURLConnection的替代者 NSURLSession的使用相对于之前的NSURLConnection更简单&#x…