iOS内存管理总结

自动引用计数,又称ARC(Automatic Reference Counting)是苹果在iOS5中引入的重要特性,它减少了我们在内存管理时的麻烦,让我们可以把更多的精力放在其它更重要的事情上。

虽然ARC给我们带来了很多方便,但如果开发者不了解基本的内存管理知识,还是会在开发工作中遇到很多问题。所以,我总结了ARC环境下应该知道的内存管理知识,供诸位参考。

基于引用计数的内存管理
要了解ARC,必须先了解Objective-C中对象的内存管理机制以及手动管理引用计数(MRR,Manual Retain-Release)。

Objective-C内存管理机制
Objective-C中的对象都是基于引用计数来管理生命周期的。简单来说就是,我们在需要持有一个对象时,调用retain让它的引用计数+1。不需要这个对象的时候,调用release让它的引用计数-1。当一个对象引用计数为0的时候,这个对象就会被自动销毁。

MRR
我们在手动管理引用计数的时候,要明确地控制对象的生命周期,显式的调用每一个retain和release。我们必须清楚的了解每个接口对引用计数的处理(如把一个对象放到数组里引用计数会被+1,用alloc创建的对象的引用计数一开始就是1,用哪些接口创建的对象是已经被调用过autorelease的等等)。在处理引用计数时稍有疏忽,就可能导致程序崩溃或内存泄漏。

ARC
ARC是编译器通过对代码的静态分析,确定对象的生命周期,并在合适的位置自动加上retain和release的机制。把内存管理交给编译器以后,我们不需要再调用任何的retain和release了。ARC减少了MRR带来的思考负担,减少了内存问题出现的可能性,也大幅减少了代码量。

Paste_Image.png
ARC的内存管理
ARC简化了引用计数的概念,它把变量对对象的引用分为强引用和弱引用两种。所有ARC下的内存管理原则都将基于这两个概念。强引用表示变量拥有对象的所有权。多个变量有可能同时持有同一个对象的强引用。当一个对象没有被任何变量强引用,它就会被释放。弱引用表示引用但不拥有对象,它可以避免两个对象相互强引用导致的内存泄漏问题。

所有权修饰符
ARC下的变量都会被加上下面几种所有权修饰符,它们指定了变量对其指向对象的所有权形式。整个ARC的规则也正是围绕着这几个修饰符运作:__strong:表示对对象的强引用。它是变量修饰符的默认值,也就是说只要没有显式的给变量加上所有权修饰符,它就是strong的。strong变量在离开作用域范围后会被废弃(其实就是编译器插入了一个release),对对象的强引用也随之消失。__weak:表示弱引用。当其指向的对象被释放时,这个变量会被置成nil。__autoreleasing:表示将修饰的对象加入autoreleasepool中,在autoreleasepool被销毁时自动释放对对象的强引用。在@autoreleasepool的代码块中的变量都会被加上这个修饰符,在超出代码块范围后释放。详见下面autorelease的部分。__unsafe_unretained:表示既不持有对象的强引用,也不持有弱引用(对象析构时它不会被置为nil)。正如它的名字描述,它是不安全的。它只是在iOS5之前用来代替__weak。

属性修饰符
在使用MRR的时候,我们可以给property添加retain、assign、copy这几种修饰符,来设置想要的内存管理模式,用下面这段代码来说明这三种修饰符的作用:

// MRR环境
@property (nonatomic, retain) NSObject* retainedObject;
@property (nonatomic, assign) NSObject* assignedObject;
@property (nonatomic, copy) NSMutableString* copiedObject;

  • (void)testProperties {
    NSObject* objectA = [[NSObject alloc] init]; // objectA引用计数为1。
    self.retainedObject = objectA; // self.retainedObject和objectA指向同一个对象,且该对象引用计数为2。

    NSObject* objectB = [[NSObject alloc] init]; // objectB引用计数为1。
    self.assignedObject = objectB; // self.assignedObject和objectB指向同一个对象,且该对象引用计数为1。

    NSMutableString* objectC = [[NSMutableString alloc] initWithFormat:@”test”]; // objectC引用计数为1
    self.copiedObject = objectC; // self.copiedObject和objectC指向两个不同的对象,两个对象引用计数都为1。

    // 这里可能会有疑问,为什么copy的例子用的是NSMutableString而不是NSObject了或者NSString?
    // 因为NSObject没有实现NSCopying协议,没法复制。
    // 而使用NSString会导致self.copiedObject和objectC因为编译器优化而指向相同的对象。

    // 因为是MRR环境,我们要释放我们自己分配的内存,否则会产生内存泄漏。
    [objectA release];
    [objectC release];
    // 当然,给分配内存的代码加上autorelease也行:
    // NSObject* objectA = [[[NSObject alloc] init] autorelease];
    // NSMutableString* objectC = [[[NSMutableString alloc] init] autorelease];
    }

  • (void)dealloc {
    // MRR要在析构时手动清理内存。
    [_objectA release];
    [_objectB release];
    [_objectC release];

    [super dealloc];
    }
    在ARC环境下,增加了两种新的修饰符:strong和weak,分别对应强引用和弱引用:

// ARC环境
@property (nonatomic, strong) NSObject* strongObject;
@property (nonatomic, weak) NSObject* weakObject;

  • (void)testProperties {
    NSObject* objectD = [[NSObject alloc] init]; // objectD持有对象D的强引用。
    self.strongObject = objectD; // self.strongObject和objectD都持有对象D的强引用

    NSObject* objectE = [[NSObject alloc] init]; // objectE持有对象E的强引用。
    self.weakObject = objectE; // self.weakObject持有对象E的弱引用

    // 在ARC环境下,这个方法执行完后,objectD和objectE对对象D和E的强引用会消失。
    // 这时候self.strongObject仍然持有对对象D的强引用。
    // self.weakObject之前对对象E持有的是弱引用,对象E析构。self.weakObject的指针被置为nil。
    }

// 与MRR不同,ARC环境下,self对象析构时,self.strongObject对对象E的强引用自动消失,对象E自动析构。
Retain Cycle(保留环)也叫循环引用
ARC确实帮助我们避免了许多内存管理的问题。但在ARC环境下,有一类问题还是需要被妥善的处理,这类问题叫做Retain Cycle(保留环)。

ARC环境下的对象在没有被强引用时就会被释放,当两个对象互相对对方持有强引用时,这两个对象就永远不会被释放了。这就导致了上面说的保留环问题。

要解决保留环的思路也简单,就是理清这两个对象之间的所有权关系,再让其中一个对象对另一个对象持有弱引用(使用weak指针)即可。

有一种保留环的问题相对隐蔽,出现在使用block的时候。block是一个可以被独立运行的代码块,为了保证它随时可以被运行,它会持有对它包含的所有变量的强引用。我们来看有问题的代码:

@property (nonatomic, strong) ExampleBlock aBlock; // self持有aBlock对象的强引用

  • (void)exampleFunction {
    self.aBlock = ^{
    [self doSomething]; // aBlock持有self的强引用
    };
    }
    上面的代码中,self和aBlock两个对象互相持有对方的强引用,导致了两个对象都无法被释放。

我们在遇到上述情况时,要让其中一个引用变为弱引用,修改后的代码如下:

@property (nonatomic, strong) ExampleBlock aBlock;

  • (void)exampleFunction {
    // 让block捕获self的弱引用
    __weak __typeof(self)weakSelf = self;
    self.aBlock = ^{
    // 把弱引用转化为强引用,防止在block处理过程中self被析构。
    __strong __typeof(weakSelf)strongSelf = weakSelf;
    [strongSelf doSomething];
    }];
    }
    autorelease
    在iOS中,autorelease的意义是稍后释放。我们先看一段MRR的代码:

// MRR
- (void)doSomething {
NSArray* arrayA = [[NSArray alloc] init];
NSArray* arrayB = [self getEmptyArray];

// 按照MRR的原则,创建的对象的地方必须负责对象的释放。这样才能保持引用计数的平衡。
// 所以这里必须调用[arrayA release];来与上面的alloc保持平衡。
[arrayA release];
// 然而arrayB是从getEmptyArray方法中得来,
// 我们并不知道getEmptyArray是创建了一个新的对象还是返回了某个类的成员变量,
// 这里释放getEmptyArray返回的对象是不合适的。

}

  • (void)getEmptyArray {
    // 我们必须在getEmptyArray的实现中来保证引用计数的平衡。
    // 如果写return [[[NSArray alloc] init] release];会返回一个nil。
    // 所以,用autorelease让新创建的对象进行稍后释放。
    return [[[NSArray alloc] init] autorelease];
    }
    稍后到底是什么时候?这个涉及到autoreleasepool的概念。
    当一个对象被autorelease的时候,它其实是被注册到最里层(autoreleasepool是类似栈的结构)的autoreleasepool里,在autoreleasepool被销毁的时候,里面所有的对象都会被销毁。
    系统会在每次消息循环开始的时候,建立一个autoreleasepool,在这一次消息循环结束后销毁这个autoreleasepool。一般情况下,这个就是最里层的autoreleasepool了。

我们可能会在一次消息循环周期内创建大量的autorelease对象。为了防止内存占用过多,我们可以手动使用@autoreleasepool代码块来创建自己的autoreleasepool,让我们创建的这些对象提前释放:

  • (void)autoreleasePoolExample {
    @autoreleasepool {
    // 在这个块范围内被autorelease的对象会加到这个新的autoreleasepool里,
    // 在这个代码块结束时就被释放。
    }
    }
    在ARC下我们不能显式调用autorelease方法,那autorelease到底会在什么时候用到呢?其实编译器已经帮我们加上了autorelease:

  • (id)getEmptyArray {
    // 在ARC下我们不能自己加上autorelease。编译器为保证平衡和返回值的有效性,
    // 会给这个方法的返回值隐式的加上autorelease。
    // 实际上,除了alloc/new/copy等开头的方法外,
    // 其它方法的返回值都会按照这个规则,被自动加上autorelease。
    return [[NSArray alloc] init];
    }
    以上是对ARC环境下iOS内存管理总结。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值