[面试时]如何讲清楚objective-c内存管理

一、概论

对C++程序员来说,使用指针最蛋疼的就是内存管理,为了避免卷进繁琐的管理内存保证不会内存泄露,我通常尽量不使用指针。但是在objective-c中,所有的变量都是指针,那么你就不得不考虑下如何管理内存了。

二、手动引用计数

在没有ARC之前,一直都是手动引用计数来管理内存,那时候内存管理严格遵循四条规则,即:

1、由自己生成的对象,自己持有
2、非自己生成的对象,自己也能持有
3、不再需要自己持有的对象时释放
4、非自己持有的对象无法释放

而objective-c也提供了相应的方法来进行对象的操作

对象操作objective-c方法
生成并持有对象alloc/new/copy/mutableCopy等方法
持有对象retain方法
释放对象release方法
废弃对象dealloc方法

对应的,1、alloc/new/copy/mutableCopy等方法生成对象,并持有对象

id obj = [[NSObject alloc] init];

2、retain方法可以让变量持有非自己生成的对象

id obj = [NSMutableArray array];
[obj retain];

3、自己生成的对象不再需要时,需要释放,这时候用到release

id obj = [NSMutableArray array];
[obj release];

4、不能够释放非自己生成的对象,否则程序会崩溃

id obj = [NSMutableArray array];
[obj release];    // 此时obj不再持有对象,对象已经被释放,retainCount为0,对象被废弃
[obj release];   //  释放非自己持有的对象,程序崩溃

此外,还有一些要点:
1、objective-c的内存管理称为“引用计数”,原因是决定生成的对象是否被废弃取决于对象的retainCount,生成时retainCount等于1,retain方法可以使retainCount加1,release可以使retainCount减1,假如retainCount为0,则调用delloc废弃该对象。

2、autorelease是objective-c另外一个关键的修饰符,意思是“自动释放”,被autorelease标志的对象会被注册到autoreleasepool中(对象的retainCount会加1),经过一个NSRunLoop(即一个响应事件),autoreleasepool会对池中的所有对象进行一次release。酱紫程序员就不需要担心对象的内存泄露了。其中有个问题,假如一个对象多次autorelease,会被多次注册到autoreleasepool中么?会被多次释放么?
NSAutoreleasePool对象的生命周期
3、引用计数的底层实现机制是用一个hash表来集中管理,key值是对象的内存块地址,hash表上会记录该对象的retainCount和内存块地址,以方便找到对应的内存块。
通过引用计数表追溯对象

三、ARC

但是程序员水平参差不齐,苹果发现很多程序都出现内存泄露问题,于是引进了ARC。总的来说,ARC和之前一样遵循内存管理的四条规则,同时定义了四个所有权修饰符,

__strong ==[retain]
__weak ~=[__unsafe_unretained]
__unsafe_unretained
__autorelease == [autorelease]

代替了手动输入retain、release、autorelease的繁琐语句。
那么ARC如何实现四条规则的?
1、__strong 表示对对象强引用,当生成自己持有的对象时,必须将变量修饰为__strong

id __strong obj = [[NSObject alloc] init];

2、__strong 也相当于retain,当非自己的持有的对象赋值给__strong修饰的变量时,retainCount会加1

id __strong obj = [NSMutableArray array]; //此时obj的retainCount 为1

3、当超出强引用变量的作用域时,retainCount会减1(不再需要自己持有的对象时释放),系统帮你管理了,你不用操心。

{
    id __strong obj = [NSMutableArray array]; //此时obj的retainCount 为1
}
//超出变量obj作用域,强引用失效,retainCount减1为0,对象没有持有者,被废弃

4、非自己持有的对象无法释放,因为ARC,系统自动帮你管理,你不用操心。

当然,这些只是ARC的一部分,还有另外的一些要点:
1、__weak修饰符之所以会出现,是为了打破循环引用。举个例子:
相互强引用

@interface Test : NSObject
{
    id __strong obj_;
}
-(void)setObject:(id __strong)obj;
@end
@implementation Test
-(id)init
{
    self = [super init];
    return self;
}
-(void)setObject:(id __strong)obj
{
    obj_ = obj;
}
{
    id __strong test1 = [[Test alloc] init];//test1的retainCount为1
    id __strong test2 = [[Test alloc] init];//test2的retainCount为1
    [test1 setObject:test2];//test2的retainCount为2
    [test2 setObject:test1];//test1的retainCount为2
}
//超出test1的作用域,强引用失效,test1的retainCount减1
//超出test2的作用域,强引用失效,test2的retainCount减1
//但是test1和test2的变量obj_仍然持有test2和test1
//发生内存泄露

以上就是产生所谓的循环引用,即双方的对象成员仍然持有双方的对象,无法释放,而__weak的修饰符就是在赋值的时候retainCount不变,上述程序如果对变量obj_ 的修饰符__strong改为__weak,则可以避免循环应用,如下:
相互弱引用

@interface Test : NSObject
{
    id __weak obj_;
}
-(void)setObject:(id __strong)obj;
@end
{
    id __strong test1 = [[Test alloc] init];//test1的retainCount为1
    id __strong test2 = [[Test alloc] init];//test2的retainCount为1
    [test1 setObject:test2];//test1的obj_弱引用test2,因此test2的retainCount仍为1
    [test2 setObject:test1];//test2的obj_弱引用test1,因此test1的retainCount仍为1
}
//超出test1的作用域,强引用失效,test1的retainCount减1
//超出test2的作用域,强引用失效,test2的retainCount减1
//但是test1和test2的retainCount都为0,因此废弃两个对象

由上可见,__weak修饰符避免了__strong引起的相互引用,因此有存在的必要。但是__weak修饰符只能在iOS4以及OS X Snow Leopard中使用,更低一级的设备就只能用__unsafe_unretained 来代替了。
另外一个问题是,弱引用如何知道指向的对象被释放从而给引用指针赋值nil?参考这篇文章中boost的弱引用的实现方法,我们可以得到答案。即使用了观察者模式,首先对将每一个弱引用添加到观察者队列,当强引用指向的对象被释放时,立即对观察者发出信息,把所有元素都置为nil,这就好像autoreleasepool的实现一样。
但是文章中只是用一个计数替代了观察者队列,这里的设计更为巧妙。实现方式是酱紫的,维持一个弱引用指针个数的变量weakcount,当强引用指向的对象被释放时,hashtable并没有立即删除对应的项目,每次有弱引用访问,weakcount减1并将弱引用置为nil,直到weakcount为0时删除hashtable中该对象对应项即可。因为每次弱引用被置为nil,以后就再也不可能进入到hashtable,从而避免了重复计数

2、__unsafe_unretained其实和__weak的作用是一样的,但是他们的区别在于__weak修饰的变量会在超出作用域时对对象的引用失效,并将变量自己置空,而__unsafe_unretained则不会,因此容易产生悬浮指针,这也就是不安全的原因。

3、__autoreleasing修饰符的作用和引用计数的autorelease相同,其次ARC中还用@autoreleasepool块来代替NSAutoreleasePool
其实在主线程中也存在autoreleasepool,但是其一般在一次runLoop后才会清空缓存池,对那些部分多次创建的对象并不能够及时释放,因此@autoreleasepool存在的意义就是手动的创建缓存池并及时自动清空,从而达到降低内存峰值的目的。而@autoreleasepool会比NSAutoreleasePool更加轻便安全。
@autoreleasepool和附有__autoreleasing修饰符的变量
4、__strong修饰符的底层实现机制实际上是retain、release不需要你自己写,编译器帮你加入进去了,如下:

栗子1:
{
    id __strong obj = [[NSObject alloc] init];
}
//编译器模拟代码
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_release(obj);
栗子2:
{
    id __strong obj = [NSMutableArray array];
}
//编译器模拟代码
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);

__autoreleasing的底层实现同__strong一样,都是编译器帮你加入了部分代码。

5、__weak修饰符的底层实现是维护一个weak表,key值为对象的内存地址,表中保存着指向该对象的所有_weak变量,对象被objc_release函数释放后,引用计数为0,对象被废弃,调用delloc函数,再间接调用(因为会按顺序调用_objc_rootDealloc、object_dispose、objc_destructInstance)objc_clear_dellocating函数,执行如下操作:

(1)从weak表中取出废弃对象地址为key值的记录
(2)将包含在记录中的所有附有__weak修饰符的变量地址,赋值为nil
(3)从weak表中删除该记录
(4)从引用计数表中删除废弃对象的地址为key值的记录

由上可见,__weak修饰的变量太多会消耗相应的CPU资源,因此最好是在避免循环引用的时候才考虑使用__weak修饰符

6、属性声明的属性与所有权修饰符的对应关系

属性声明的属性所有权修饰符
assign__unsafe_unretained修饰符
copy__strong修饰符
retain__strong修饰符
strong__strong修饰符
unsafe_unretained__unsafe_unretained修饰符
weak__weak修饰符

7、ARC只负责管理Objective-C对象的内存,CoreFoundation对象不归ARC管理,因此开发者必须适时调用CFRetain/CFRelease函数。objective-c对象与Core Foundation(主要有C语言编写)对象之间的转换需要用到三个转换符,分别是

__bridge
__bridge_retained ==[retain]
__bridge_transfer ==[release]

(1)__brige的栗子:

//ARC无效
id obj = [[NSObject alloc] init];
void *p = obj;
id o = p;
[o release];

//ARC有效 需要修改成为如下才不用引起编译错误
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id o = (__bridge id)p;

(2)__bridge_retained的栗子

//ARC无效
id obj = [[NSObject alloc] init];
void *p = obj;
[(id)p retain];

//ARC有效 意思就是使被赋值的变量也能持有该对象
id obj = [[NSObject alloc] init];
void *p = (__bridge_retained void *)obj;

(3)__bridge_transfer的栗子

//ARC无效
id obj = [[NSObject alloc] init];
void *p = (void *)obj;
[(id)p retain];
[obj release];

//ARC有效 意思就是使被赋值的变量也能持有该对象,而赋值的变量赋值后释放该对象
void *p = (__bridge\_transfer void *)obj;

8、ARC需要遵循的规则:

(1) 不能使用retain/release/retainCount/autorelease
(2) 不能使用NSAllocateObject/NSDeallocateObject
(3) 须遵守内存管理的方法命名规则
(4) 不要显式的调用delloc
(5) 使用@autoreleasepool块代替NSAutoreleasePool
(6) 不能使用区域(NSZone)
(7) 对象型变量不能作为C语言结构体(struct/union)的成员
(8) 显式的转换“id”和“void*”

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值