内存管理—MRC时代的手动内存管理
内存管理—weak的实现原理
内存管理——autorelease原理分析
内存管理——定时器问题
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中的字典)。
我们可以给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;
}
*******************运行结果********************
当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_clear_no_lock(&table.weak_table, (id)this);
来处理__weak
指针的,其中第一个参数就是sideTable
的成员weak_table
,第二个参数就是需要被释放的对象。我们看看该函数的内部逻辑
这里的核心方法是weak_entry_for_referent
再点进去
很明显,上面的方法里面,通过需要释放的对象referent
根据一定的算法得出一个索引index
,然后再从weak_table
里面利用index
拿到对象referent
所对应的weak
指针,这就说明weak_table
内部其实就是一个散列表结构,通过对象作为key
,value
就是指向该对象的weak
指针组成的数组。
- 当一个对象
obj
被weak
指针指向时,这个weak
指针会以obj
作为key
,被存储到sideTable
类的weak_table
这个散列表上对应的一个weak指针数组里面。- 当一个对象
obj
的dealloc
方法被调用时,Runtime会以obj
为key
,从sideTable
的weak_table
散列表中,找出对应的weak
指针列表,然后将里面的weak
指针逐个置为nil
。
以上就是weak
指针的实现原理