ARC下需要注意的内存管理

ARC下需要注意的内存管理

2016/04/03 · iOS开发 · 内存管理

分享到:1

原文出处: 一不(@luoyibu)

之前发了一篇关于图片加载优化的文章,还是引起很多人关注的,不过也有好多人反馈看不太懂,这次谈谈iOS中ARC的一些使用注意事项,相信做iOS开发的不会对ARC陌生啦。
这里不是谈ARC的使用,只是介绍下ARC下仍然可能发生的内存泄露问题,可能不全,欢迎大家补充。

Ps:关于ARC的使用以及内存管理问题,强烈建议看看官方文档,里面对内存管理的原理有很详细的介绍,相信用过MRC的一定看过这个。

另也有简单实用的ARC使用教程:ARC Best Practices


在2011年的WWDC中,苹果提到90%的crash是由于内存管理引起的,ARC(Automatic Reference Counting)就是苹果给出的解决方案。启用ARC后,开发者不需要担心内存管理,编译器会为你处理这一切(注意ARC是编译器特性,而不是iOS运行时特性,更不是其他语言中的垃圾收集器)。
简单来说,编译器在编译代码时,会自动生成实例的引用计数代码,帮助我们完成之前MRC需要完成的工作,不过据说除此之外,编译器也会执行某些优化。

ARC虽然能够解决大部分的内存泄露问题,但是仍然有些地方是我们需要注意的。

循环引用

循环引用简单来说就是两个对象相互强引用了对方,即retain了对方,从而导致谁也释放不了谁的内存泄露问题。比如声明一个delegate时一般用weak而不能用retain或strong,因为你一旦那么做了,很大可能引起循环引用。

这种简单的循环引用只要在coding的过程中多加注意,一般都可以发现。
解决的办法也很简单,一般是将循环链中的一个强引用改为弱引用就可解决。
另外一种block引起的循环引用问题,通常是一些对block原理不太熟悉的开发者不太容易发现的问题。

block引起的循环引用

我们先看看官方文档关于block调用时的解释:Object and Block Variables

When a block is copied, it creates strong references to object variables used within the block. If you use a block within the implementation of a method:

  1. If you access an instance variable by reference, a strong reference is made to self;
  2. If you access an instance variable by value, a strong reference is made to the variable.

主要有两条规则:
第一条规则,如果在block中访问了属性,那么block就会retain住self。
第二条规则,如果在block中访问了一个局部变量,那么block就会对该变量有一个强引用,即retain该局部变量。

根据这两条规则,我们可以知道发生循环引用的情况:

1

2

3

4

5

6

7

8

9

10

11

//规则1

self.myblock = ^{

[self doSomething];           // 访问成员方法

NSLog(@"%@", weakSelf.str);   // 访问属性

};

//规则2

ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];

[request setCompletionBlock:^{

NSString* string = [request responseString];

}];

对象对block拥有一个强引用,而block内部又对外部对象有一个强引用,形成了闭环,发生内存泄露。

怎么解决这种内存泄露呢?
可以用block变量来解决,首先还是看看官方文档怎么说的:
Use Lifetime Qualifiers to Avoid Strong Reference Cycles

In manual reference counting mode, __block id x; has the effect of not retaining x. In ARC mode, __block id x; defaults to retaining x (just like all other values). To get the manual reference counting mode behavior under ARC, you could use __unsafe_unretained __block id x;. As the name __unsafe_unretained implies, however, having a non-retained variable is dangerous (because it can dangle) and is therefore discouraged. Two better options are to either use__weak (if you don’t need to support iOS 4 or OS X v10.6), or set the __blockvalue to nil to break the retain cycle.

官网提供了几种方案,我们看看第一种,用__block变量:

在MRC中,__block id x不会retain住x;但是在ARC中,默认是retain住x的,我们需要
使用__unsafe_unretained __block id x来达到弱引用的效果。

那么解决方案就如下所示:

1

2

3

4

5

6

__block id weakSelf = self;  //MRC

//__unsafe_unretained __block id weakSelf = self;   ARC下面用这个

self.myblock = ^{

[weakSelf doSomething];

NSLog(@"%@", weakSelf.str);

};

1

答案很简单,系统会自动retain住其接收者,直到其执行我们指定的方法。

看看官方的文档吧,也建议你自己写个demo测试一下。

1

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats

1

2

3

4

5

6

7

8

target

The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to target until it (the timer) is invalidated. (系统会维护一个强引用直到timer调用invalidated)

userInfo

The user info for the timer. The timer maintains a strong reference to this object until it (the timer) is invalidated. This parameter may be nil.

repeats

If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.

可以注意到repeats参数,一次性(repeats为NO)的timer会再触发后自动调用invalidated,而重复性的timer则不会。
现在问题又来了,看看下面这段代码:

1

2

3

4

5

- (void)dealloc

{

[timer invalidate];

[super dealloc];

}

这个是很容易犯的错误,如果这个timer是个重复性的timer,那么self对象就会被timerretain住,这个时候不调用invalidate的话,self对象的引用计数会大于1,dealloc永远不会调用到,这样内存泄露就会发生。

1

timer都会对它的target进行retain,我们需要小心对待这个target的生命周期问题,尤其是重复性的timer,同时需要注意在dealloc之前调用invalidate。

关于timer其实有挺多可以研究的,比如其必须在runloop中才有效,比如其时间一定是准的吗?这些由于和本章主题不相关,暂时就不说了。

关于performSelector:afterDelay的问题

1

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay

我们还是看看官方文档怎么说的,同样也希望大家能写个demo验证下。

1

This method sets up a timer to perform the aSelector message on the current thread’s run loop. The timer is configured to run in the default mode (NSDefaultRunLoopMode). When the timer fires, the thread attempts to dequeue the message from the run loop and perform the selector. It succeeds if the run loop is running and in the default mode; otherwise, the timer waits until the run loop is in the default mode.

大概意思是系统依靠一个timer来保证延时触发,但是只有在runloopdefault mode的时候才会执行成功,否则selector会一直等待run loop切换到default mode
根据我们之前关于timer的说法,在这里其实调用performSelector:afterDelay:同样会造成系统对target强引用,也即retain住。这样子,如果selector一直无法执行的话(比如runloop不是运行在default model下),这样子同样会造成target一直无法被释放掉,发生内存泄露。
怎么解决这个问题呢?
其实很简单,我们在适当的时候取消掉该调用就行了,系统提供了接口:

1

+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget

这个函数可以在dealloc中调用吗,大家可以自己思考下?
关于NSNotification的addObserver与removeObserver问题
我们应该会注意到我们常常会再dealloc里面调用removeObserver,会不会上面的问题呢?
答案是否定的,这是因为addObserver只会建立一个弱引用到接收者,所以不会发生内存泄露的问题。但是我们需要在dealloc里面调用removeObserver,避免通知的时候,对象已经被销毁,这时候会发生crash.

C 语言的接口

C 语言不能够调用OC中的retain与release,一般的C 语言接口都提供了release函数(比如CGContextRelease(context c))来管理内存。ARC不会自动调用这些C接口的函数,所以这还是需要我们自己来进行管理的.

下面是一段常见的绘制代码,其中就需要自己调用release接口。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

CGContextRef context = CGBitmapContextCreate(NULL, target_w, target_h, 8, 0, rgb, bmi);

CGColorSpaceRelease(rgb);

UIImage *pdfImage = nil;

if (context != NULL) {

CGContextDrawPDFPage(context, page);

CGImageRef imageRef = CGBitmapContextCreateImage(context);

CGContextRelease(context);

pdfImage = [UIImage imageWithCGImage:imageRef scale:screenScale orientation:UIImageOrientationUp];

CGImageRelease(imageRef);

} else {

CGContextRelease(context);

}

总的来说,ARC还是很好用的,能够帮助你解决大部分的内存泄露问题。所以还是推荐大家直接使用ARC,尽量不要使用mrc。

参考文献

  1. Transitioning to ARC Release Notes
  2. iOS应用开发:什么是ARC?
  3. Blocks, Operations, and Retain Cycles
  4. iOS7.0 使用ARC
  5. block使用小结、在arc中使用block、如何防止循环引用
  6. IOS中关于NSTimer使用知多少
  7. 正确使用Block避免Cycle Retain和Crash
时间: 2024-08-09 11:47:19

ARC下需要注意的内存管理的相关文章

ARC下面的Block对内存的管理方式

一.问题引入 近日开发中引入一个随机crash,Crash堆栈如下: Exception Type: SIGSEGV Exception Codes: SEGV_ACCERR at 0x0000000101850148 Crashed Thread: 0 Thread 0 Crashed: 0 libobjc.A.dylib 0x00000001802601a0 objc_retain + 16 1 CoreFoundation 0x0000000180f593a0 -[__NSDictiona

ARC下需要注意的内存问题

之前发了一篇关于图片加载优化的文章,还是引起很多人关注的,不过也有好多人反馈看不太懂,这次谈谈iOS中ARC的一些使用注意事项,相信做iOS开发的不会对ARC陌生啦.这里不是谈ARC的使用,只是介绍下ARC下仍然可能发生的内存泄露问题,可能不全,欢迎大家补充. Ps:关于ARC的使用以及内存管理问题,强烈建议看看官方文档,里面对内存管理的原理有很详细的介绍,相信用过MRC的一定看过这个. 另也有简单实用的ARC使用教程:ARC Best Practices 在2011年的WWDC中,苹果提到90

操作系统核心原理-5.内存管理(下):分页内存管理

在上一篇介绍的几种多道编程的内存管理模式中,以交换内存管理最为灵活和先进.但是这种策略也存在很多重大问题,而其中最重要的两个问题就是空间浪费和程序大小受限.那么有什么办法可以解决交换内存存在的这些问题呢?答案是分页,它是我们解决交换缺陷的“不二法门”. 一.分页内存管理 1.1 解决问题之道 为了解决交换系统存在的缺陷,分页系统横空出世.分页系统的核心在于:将虚拟内存空间和物理内存空间皆划分为大小相同的页面,如4KB.8KB或16KB等,并以页面作为内存空间的最小分配单位,一个程序的一个页面可以

MRC、ARC内存管理机制

MRC下,oc内存管理遵循"谁创建.谁释放.谁引用.谁管理"的机制,当创建或引用一个对象时,需要向她发送alloc,copy,retain消息,当释放该对象时需要发送release消息,当引用计数为零的时候,系统释放该对象. ARC是自动引用计数,管理机制与手动机制一样,只是不再需要调用retain,release,autorelease,它会在适当的位置插入release和autorelease.使用ARC不代表不需要内存管理了,例如使用block时要避免循环引用,代理作为属性时,要

Swift2.0(17)内存管理ARC

自动引用计数ARC 由于Swift出现的比较晚,所有并不像OC那样有ARC和MRC两种内存管理方式 在Swift统一使用非常方便的ARC进行管理内存 自动引用计数,本质上仍然是面向引用计数管理实例对象被释放的时机 自动的含义是并不直接访问引用计数,而是统一判断准则在使用上使其透明 管理范畴:类类型的实例对象 强弱引用 强引用,即Objective-C中的强指针 也就说是:当没有强引用指向实例对象时,该实例对象销毁 默认情况下,所有引用都是强引用 弱引用,即Objective-C中的弱指针 弱引用

内存管理总结-ARC和非ARC

序言 ObjC内存管理分为两个阶段: Xcode4之前,ObjC的内存管理就需要由开发人员手动维护,内存的管理需要开发人员自己调用内存管理的方法,所以被称为MRC(Manual Reference Counting),即非ARC. Xcode4之后,ObjC的内存管理就不需要由开发人员手动维护,系统会自定在合适的时机或位置帮助开发人员调用内存管理的方法,所以被称为ARC(Automatic Reference Counting). 我们知道在程序运行过程中要创建大量的对象,和其他高级语言类似,在

Delphi中ARC内存管理的方向

随着即将发布的10.3版本,RAD Studio R&D和PM团队正在制作Delphi在内存管理方面的新方向. 几年前,当Embarcadero开始为Windows以外的平台构建新的Delphi编译器时,就核心语言功能和语言的整体感知而言,有很多讨论新Delphi与当前语言的兼容性.最终出现的决定是保持极高程度的兼容性,并采用一些重要而大胆的步骤来实现更能吸引新一代开发人员的语言. 什么是自动参考计数? (具有弱引用的交叉链接对象) 其中一个变化就是决定采用新的移动平台内存管理模式,遵循Appl

【iOS开发-33】学习手动内存管理临时抛弃ARC以及retain/assign知识——iOSproject师面试必考内容

我们为什么须要内存管理?当使用内存达到40M和45M时候会发出警告,假设不处理,占用内存达到120M时直接强制关闭程序. 所以出现闪退除了是程序出现逻辑错误,还有可能是内存使用过大. (1)创建一个对象的过程:先分配内存空间存储对象:初始化成员变量:返回对象的指针. (2)对象在创建时,内部会自己主动创建一个引用计数器retainCount,当retainCount=0时,系统会回收当前对象,retainCount是唯一推断标记.release会-1.retain会+1,retain后返回的是自

【iOS开发-33】学习手动内存管理暂时抛弃ARC以及retain/assign知识——iOS工程师面试必考内容

我们为什么需要内存管理?当使用内存达到40M和45M时候会发出警告,如果不处理,占用内存达到120M时直接强制关闭程序.所以出现闪退除了是程序出现逻辑错误,还有可能是内存使用过大. (1)创建一个对象的过程:先分配内存空间存储对象:初始化成员变量:返回对象的指针. (2)对象在创建时,内部会自动创建一个引用计数器retainCount,当retainCount=0时,系统会回收当前对象,retainCount是唯一判断标记.release会-1,retain会+1,retain后返回的是自己se