天天看点

内存管理—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

存不下引用计数的话,那么引用计数就会被存放在

SideTable

refcnts

中,从类型RefcountMap可以看出,它实际上是一个散列表的结构(类似OC中的字典)。

weak指针实现原理

我们可以给property属性设置

strong

weak

unsafe_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的实现原理

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的实现原理
内存管理—weak的实现原理

可以看到这两个函数内部都是通过调用

weak_clear_no_lock(&table.weak_table, (id)this);

来处理

__weak

指针的,其中第一个参数就是

sideTable

的成员

weak_table

,第二个参数就是需要被释放的对象。我们看看该函数的内部逻辑

内存管理—weak的实现原理

这里的核心方法是

weak_entry_for_referent

再点进去

内存管理—weak的实现原理

很明显,上面的方法里面,通过需要释放的对象

referent

根据一定的算法得出一个索引

index

,然后再从

weak_table

里面利用

index

拿到对象

referent

所对应的

weak

指针,这就说明

weak_table

内部其实就是一个散列表结构,通过对象作为

key

value

就是指向该对象的

weak

指针组成的数组。

weak实现原理总结

  • 当一个对象

    obj

    weak

    指针指向时,这个

    weak

    指针会以

    obj

    作为

    key

    ,被存储到

    sideTable

    类的

    weak_table

    这个散列表上对应的一个weak指针数组里面。
  • 当一个对象

    obj

    dealloc

    方法被调用时,Runtime会以

    obj

    key

    ,从

    sideTable

    weak_table

    散列表中,找出对应的

    weak

    指针列表,然后将里面的

    weak

    指针逐个置为

    nil

    以上就是

    weak

    指针的实现原理