记录整理一下我所理解的OC的内存管理,以及关于内存管理的一些思考(问题)。
OC内存管理机制是什么?
ARC(Automatic Reference Counting),自动引用计数,是OC的内存管理机制。简单的来说,就是编译器在编译时期,会帮我们自动插入retain,release代码。我们不用像MRC时期,手动retain对象获取内存,release对象释放内存。
引用计数
此处要说个例子,让我对内存管理有了更深理解的例子~
假设办公室的灯只有一个,有人进来,就开关。就人出去,就关灯。如果每个人进来时开灯,出去时关灯,那么肯定会出现办公室里面有人,灯确是关的状态。
解决这一问题的办法是:办公室里第一个人来的时候开灯,最后一个人离开的时候关灯。如下所示
办公室人数 | 引用计数 | 灯的状态 | 对OC对象所做的动作 | 我的理解 |
---|---|---|---|---|
0个人 | 0 | 关灯 | - | 把灯当成一个实例对象 |
进来1个人 | 1 | 开灯 | 生成并持有对象 | 开辟一块儿内存地址,且有一指针指向对象的地址 |
又进来1个人 | 2 | 开灯 | 持有对象(retain) | 多了一个指针指向内存地址 |
走了1个人 | 1 | 开灯 | 释放对象(release) | 少了一个指针指向内存地址 |
又走了1个人 | 0 | 关灯 | 废弃对象(dealloc) | 没有指针指向内存,内存被释放掉 |
OC对象通过引数计数也能得到很好的管理。上表中,生成并持有对象时,分两种
- 持有自己生成的对象。(alloc,new,copy,mutableCopy等)
- 持有非自己生成的对象。(除了第一种的,常见的array,dictionary这种类方法等)
有什么区别呢?代码(MRC环境)如下:
//自己生成的对象自己持有
NSMutableArray *array = [[NSMutableArray alloc] init];
//不需要retain。不需要对象时,进行释放。
[array release];
//持有非自己生成的对象
NSMutableArray *array = [NSMutableArray array];//对象被注册到autoreleasepool中,当pool结束时会自动调用release。
//需要retain来持有对象,在不需要对象时,进行释放。
[array retain];
[array release];
但是,在ARC下,不管是自己生成的还是非自己生成的,用strong之后,系统会自动判断,让我们生成并持有。
与内存管理有关的关键字
strong
strong 表示对对象的强引用,指向并拥有该对象,其修饰的对象,引用计数会+1。
只要有任何一个强引用指向对象,对象都不会被释放。只有对象在引用计数为0,或者强行将其置为nil时被销毁。
在arc中,对象类型和id类型的变量默认都是用strong来修饰的。上面持有非自己生成的对象例子,在ARC里面,代码如下:
{
NSMutableArray *array = [NSMutableArray array];
}
array是用__strong修饰符来修饰,在作用域结束时,会对对象进行release。
strong对象之间赋值
- 有两个对象,obj0和obj1,里面有个strong修饰的TestObject的属性。
{
TestObject obj0 = [[TestObject alloc] init];//obj0对 对象0 强引用
TestObject obj1 = [[TestObject alloc] init];//obj1对 对象1 强引用
obj0 = obj1;//obj0重新赋值,对 obj1持有的对象1 强引用,对 原持有对象0的强引用消失
}
也就是说对象1有2个强引用,对象0无引用被废弃。出作用域后,obj0和obj1自动释放自己强持有的对象。对象1无强引用,也会被废弃。 如图所示
- obj0里面有个strong修饰的TestObject *obj,obj1里有个strong修饰的TestObject *obj。
{
TestObject obj0 = [[TestObject alloc] init];//obj0对 对象0 强引用
TestObject obj1 = [[TestObject alloc] init];//obj1对 对象1 强引用
obj0.obj = obj1;//对象0的obj变量 对 对象1 强引用
obj1.obj = obj0;//对象1的obj变量 对 对象0 强引用
}
对象0有两个强引用,obj0和obj1.obj。对象1有两个强引用,obj1和obj0.obj
出了作用域后,obj0和obj1释放对自己持有对象的引用。此时对象0有一个obj1.obj的引用,对象1有一个obj0.obj的引用。导致对象0和对象1都释放不掉。这也就造成了循环引用问题,引发内存泄露。
weak
weak表示对对象的弱引用,指向但并不拥有该对象,其修饰的对象引用计数不会增加。 无须手动设置,该对象会自行在内存中销毁。
weak可用来解决循环引用问题。比如上面的,
- 让其中一方的obj变量用strong修饰改成weak。
- 那么出了作用域后,obj0和obj1释放对自己持有对象的引用,此时对象0没有obj1.obj的强引用,对象0释放。
- obj0.obj释放对对象1的强引用,对象1也就释放掉了。
assign
assign主要用于修饰基本数据类型,比如NSInteger和CGFloat,这些数值主要存在于栈中。
assign一般不用来修饰对象。因为assign修饰的对象被释放掉后,指针地址依然存在,会造成"野指针",在堆上易崩溃。而栈上的内存系统会自动处理。
copy
与strong类似。不同的是,strong是多个指针指向同一地址,而copy会通过NSCopying接口的copyWithZone:方法复制一份新的地址,指针指向不同的地址。一般用来修饰有对应可变类型的不可变对象上,比如NSString,NSArray和NSDictionary。
思考
下面这种情况会造成循环引用吗?循环引用是什么?
以delegate为例。
- 有两个vc,AViewController , BViewController。
- vcB.delegate = vcA。且vcB的delegate属性是用strong来修饰的。
vcA代码如下。
BViewController *vcB = [[BViewController alloc] init];
vcB.delegate = self;
[self.navigationController pushViewController:vcB animated:YES];
上面这种情况并不会造成循环引用,因为当vcB pop回来后,并没有对象再持有vcB。
循环引用是指两个或两个以上对象互相强引用,导致所有对象无法释放的现象。
如果将vcB改成self.vcB。且vcB是用strong修饰的,那么会导致循环引用,因为vcA对vcB强引用,而vcB的delegate又对vcA强引用,所以当vcA被pop回来后vcA和vcB互相持有,导致释放不掉。
当然此时将delegate改成weak修饰,也会有内存问题。当vcB pop回来后,由于vcA还持有vcB,所以会导致vcB延迟释放,需要等vcA释放后,vcB才会dealloc。
NSTimer为什么会造成内存泄露?怎么解决?
self强引用timer,timer会被加到Runloop中。所以timer不销毁,self也不会被销毁。
常见的解决方法有:
- 手动调用invalidate。可在viewWillDisappear或者didMoveToParentViewController里面直接调用invalidate并置为nil。
- 自定义timer对象,在dealloc的时候调用此timer对象的方法(方法实现是对对象里面的timer invalidate并置为nil)。
dealloc什么时候会走?里面一般用来做什么操作?
当对象的引用记数为0时,对象会调用dealloc。 在dealloc中,我们一般会释放观察的行为,比如通知移除、kvo监听移除等
引用计数的值是存在对象中吗?
苹果并没有把引用计数的值放在对象中,而是通过散列表(引用计数表)来管理引用计数的,key是对象内存地址,value是它所对象的引用计数。
autorelease 原理是什么
autorelease使对象在超出生命周期后能正确的被释放(通过调用release方法)。在调用release 后,对象会被立即释放,而调用 autorelease 后,对象不会被立即释放,而是注册到 autoreleasepool 中,经过一段时间后 pool结束,此时调用release方法,对象被释放。
autorelease的核心可以说是AutoreleasePoolPage这个类
- 进释放池时:objc_autoreleasePoolPush()。
- 出释放池是:objc_autoreleasePoolPop() 以上两个方法都在AutoreleasePoolPage这个类里面,里面有个栈,有一个对象注册进来,就在栈顶加一个。pop后,栈内对象调用release方法。 具体可见 blog.sunnyxx.com/2014/10/15/…
autorelease对象什么时候释放?
autorelease对象是被注册到autoreleasePool中,在pool结束时调用release,释放对象。pool不结束,autorelease对象不会被释放。在一次runloop结束后,autorelease对象会被释放掉。
autorelease对象一定会被加进自动释放池吗?比如下面这种
{
id __strong obj = [NSMutableArray array];
}
这种并不会被加进自动释放池。模拟的编译器代码如下
{
id obj = objc_msgSend(NSMutableArray), @selector(array));
objc_retainAutoreasedReturnValue(obj);
objc_release(obj);
}
+ (id)array {
id obj = objc_msgSend(NSMutableArray, @selector(alloc));
objc_msgSend(obj, @selector(init));
return objc_autoreleaseReturnValue(obj);
}
objc_autoreleaseReturnValue和objc_autorelease方法不同,该方法不仅仅是加入自动释放池,如果后面跟着 objc_retainAutoreasedReturnValue,那么就不将返回的对象放到自动释放池,而是直接传递给方法或者函数的调用方。而objc_retainAutoreasedReturnValue 方法与objc_retain方法不同,即使对象没被注册到自动释放池中,也可以获取到对象。通过这两个函数,不将对象注册到自动释放池中,而是直接传递,这一过程达到了最优化。
weak和assign的区别是什么?是怎么把对象置为nil的。
weak一般用来修饰oc对象,而assign用来修饰基本数据。因为用weak修饰的对象被释放时,会自动置为nil,而assign不会,易造成野指针,程序崩溃。
和上面的引用计数表一样,weak对象也是通过散列表(weak表)来实现的。weak所指向的对象的地址作为key,weak对象放在value数组中。当weak所指向的对象被释放时,就会以此对象地址为key,从weak表中找到value数组里面的所有weak对象,将其置为nil。
可变的数组、字典等对象用copy修饰会发生什么?
可变的数组、字典等对象用copy修饰后会被copy出来一份不可变的对象。
一般如果对象分可变和不可变,那么可变对象都用strong来修饰,不可变的对象用copy来修饰。