内存管理—weak的实现原理

内存管理系列文章

内存管理—MRC时代的手动内存管理
内存管理—weak的实现原理
内存管理——autorelease原理分析
内存管理——定时器问题
iOS程序的内存布局


iOS引用计数的存储

我在isa的深入体会一文中介绍过,苹果从arm64架构开始,对isa进行了优化,通过位域计数将更多信息存储在了isa指针当中,充分利用了isa的内存空间。目前isa的结构如下

union isa_t 
{
    Class cls;
    uintptr_t bits;
    struct {
        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;
    };

其中的extra_rc就是用来存放引用计数的,它使用了isa上面的19个二进制位作为存储空间。extra_rc这个命名含义是额外的引用计数,也就是除了创建时候的那一次retain操作之外,在其他时刻对象进行过retain操作的次数。因此一个对象实际的引用计数 = extra_rc + 1(创建的那一次)。当然extra_rc能够表达的数量也是有限的,当对象的引用超过了extra_rc的表示范围之后,isa内部的has_sidetable_rc,用来指示对象的引用计数无法存储在isa当中,并且将引用计数的值存放到一个叫SideTable的类的属性当中。SideTable的定义可以在objc源码NSObject.mm文件中找到。如下

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;
    }

如果isa存不下引用计数的话,那么引用计数就会被存放在SideTablerefcnts中,从类型RefcountMap可以看出,它实际上是一个散列表的结构(类似OC中的字典)。

weak指针实现原理

我们可以给property属性设置strongweakunsafe_unretained,转化到成员变量上分别是__strong__weak__unsafe_unretained.下面我们来看一下他们的区别,我们通过下面几段代码案例以及运行结果来逐个说明

*********************mian.m***************************
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSLog(@"临时作用域开始");
        {
            CLPerson *person = [[CLPerson alloc] init];
            NSLog(@"person对象:%@", person);
		}
        NSLog(@"临时作用域结束");
		
		NSLog(@"strongPerson:%@", strongPerson);
            
    }
    return 0;
}


*******************运行结果********************
2019-09-02 19:59:00.835983+0800 Block学习[24021:2941713] 临时作用域开始
2019-09-02 19:59:00.836482+0800 Block学习[24021:2941713] person对象:<CLPerson: 0x100704a60>
2019-09-02 19:59:00.836541+0800 Block学习[24021:2941713] -[CLPerson dealloc]
2019-09-02 19:59:00.836575+0800 Block学习[24021:2941713] 临时作用域结束
Program ended with exit code: 0

上面这个案例很清晰的说明,局部变量person在出了临时作用域之后,就释放了。


*********************mian.m***************************
int main(int argc, const char * argv[]) {
    @autoreleasepool {
      
        __strong CLPerson *strongPerson;
        
        NSLog(@"临时作用域开始");
        {
            CLPerson *person = [[CLPerson alloc] init];
            NSLog(@"person对象:%@", person);
            strongPerson = person;
		}
        NSLog(@"临时作用域结束");
            
        NSLog(@"strongPerson:%@", strongPerson);
    }
    return 0;
}


*******************运行结果********************
2019-09-02 20:00:07.368972+0800 Block学习[24033:2942509] 临时作用域开始
2019-09-02 20:00:07.369392+0800 Block学习[24033:2942509] person对象:<CLPerson: 0x1007003c0>
2019-09-02 20:00:07.369430+0800 Block学习[24033:2942509] 临时作用域结束
2019-09-02 20:00:07.369442+0800 Block学习[24033:2942509] strongPerson:<CLPerson: 0x1007003c0>
2019-09-02 20:00:07.369460+0800 Block学习[24033:2942509] -[CLPerson dealloc]
Program ended with exit code: 0

person被作用域外的__strong指针指向时,可以看到临时作用域结束之后,person对象并没有被销毁,说明__strong指针增加了person的引用计数


*********************mian.m***************************
int main(int argc, const char * argv[]) {
    @autoreleasepool {
      
        __weak CLPerson *weakPerson;
                
        NSLog(@"临时作用域开始");
        {
            CLPerson *person = [[CLPerson alloc] init];
            NSLog(@"person对象:%@", person);
            weakPerson = person;
		}
        NSLog(@"临时作用域结束");
        
        NSLog(@"weakPerson:%@", weakPerson);    
    }
    return 0;
}


*******************运行结果********************
2019-09-02 21:48:58.332332+0800 Block学习[24180:2987983] 临时作用域开始
2019-09-02 21:48:58.332851+0800 Block学习[24180:2987983] person对象:<CLPerson: 0x100600d20>
2019-09-02 21:48:58.332889+0800 Block学习[24180:2987983] -[CLPerson dealloc]
2019-09-02 21:48:58.332920+0800 Block学习[24180:2987983] 临时作用域结束
2019-09-02 21:48:58.332938+0800 Block学习[24180:2987983] weakPerson:(null)
Program ended with exit code: 0

person被作用域外的__weak指针指向时,可以看到临时作用域结束之后,person和第一种情况一样,直接释放了,说明__weak指针没有增加person的引用计数,并且,person释放时候,__weak指针被置为nil,防止了野指针错误


*********************mian.m***************************
int main(int argc, const char * argv[]) {
    @autoreleasepool {
      
        __unsafe_unretained CLPerson *unsafePerson;
        
        NSLog(@"临时作用域开始");
        {
            CLPerson *person = [[CLPerson alloc] init];
            NSLog(@"person对象:%@", person);
			unsafePerson = person; 
		}
        NSLog(@"临时作用域结束");
        
        NSLog(@"unsafePerson:%@", unsafePerson);    
    }
    return 0;
}


*******************运行结果********************

内存管理—weak的实现原理_第1张图片
person被作用域外的__unsafe_unretained指针指向时,可以看到临时作用域结束之后,person和第一种情况一样,直接释放了,说明__unsafe_unretained指针也没有增加person的引用计数,但是最后却出现了EXC_BAD_ACCESS报错,说明是野指针问题。

这样就看出了__weak__unsafe_unretained的区别就是前者会在对象被释放的时候自动置为nil,而后者却不行。 】那么苹果是如何实现对象释放后,自动将__weak指针清空的呢?下面我们就从源码来挖掘一下。

因为__weak指针是在对象释放的时候被清空的,所以我们从对象的dealloc方法入手,我们可以在objc源码的NSObject.mm文件中找到dealloc的实现如下

- (void)dealloc {
    _objc_rootDealloc(self);
}
**********************************
void _objc_rootDealloc(id obj)
{
    assert(obj);

    obj->rootDealloc();
}
***********************************
inline void objc_object::rootDealloc()
{
	//?如果是Tagged Pointer,就直接返回
    if (isTaggedPointer()) return;  // fixme necessary?

	/*
	?如果同时满足 
	1. 是优化过的isa、
	2. 没有被weak指针引用过、
	3. 没有关联对象、
	4. 没有C++析构函数、
	5. 没有sideTable,
	就可以直接释放内存free()
	*/
    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((id)this);
    }
}

进入object_dispose函数

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

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}

*******************************
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.
        //?如果有C++析构函数,就调用一下
        if (cxx) object_cxxDestruct(obj);
        //?如果有关联对象,就进行关联对象移除操作
        if (assoc) _object_remove_assocations(obj);
        //?完了之后调用下面的函数
        obj->clearDeallocating();
    }

    return obj;
}

*********************************
inline void  objc_object::clearDeallocating()
{
    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_clearDeallocating函数,如果是个优化过isa,那么就走clearDeallocating_slow函数。我们查看一下这两个函数
内存管理—weak的实现原理_第2张图片
内存管理—weak的实现原理_第3张图片
可以看到这两个函数内部都是通过调用weak_clear_no_lock(&table.weak_table, (id)this);来处理__weak指针的,其中第一个参数就是sideTable的成员weak_table,第二个参数就是需要被释放的对象。我们看看该函数的内部逻辑
内存管理—weak的实现原理_第4张图片
这里的核心方法是weak_entry_for_referent再点进去
内存管理—weak的实现原理_第5张图片
很明显,上面的方法里面,通过需要释放的对象referent根据一定的算法得出一个索引index,然后再从weak_table里面利用index拿到对象referent所对应的weak指针,这就说明weak_table内部其实就是一个散列表结构,通过对象作为keyvalue就是指向该对象weak指针组成的数组。

weak实现原理总结

  • 当一个对象objweak指针指向时,这个weak指针会以obj作为key,被存储到sideTable类的weak_table这个散列表上对应的一个weak指针数组里面。
  • 当一个对象objdealloc方法被调用时,Runtime会以objkey,从sideTableweak_table散列表中,找出对应的weak指针列表,然后将里面的weak指针逐个置为nil
    以上就是weak指针的实现原理

你可能感兴趣的:(iOS,-,OC底层原理深入)