以前发了一篇关于图片加载优化的文章,仍是引发不少人关注的,不过也有好多人反馈看不太懂,此次谈谈iOS中ARC的一些使用注意事项,相信作iOS开发的不会对ARC陌生啦。
这里不是谈ARC的使用,只是介绍下ARC下仍然可能发生的内存泄露问题,可能不全,欢迎你们补充。html
Ps:关于ARC的使用以及内存管理问题,强烈建议看看官方文档,里面对内存管理的原理有很详细的介绍,相信用过MRC的必定看过这个。ios
另也有简单实用的ARC使用教程:ARC Best Practicesapp
在2011年的WWDC中,苹果提到90%的crash是因为内存管理引发的,ARC(
Automatic Reference Counting
)就是苹果给出的解决方案。启用ARC后,开发者不须要担忧内存管理,编译器会为你处理这一切(注意ARC是编译器特性,而不是iOS运行时特性,更不是其余语言中的垃圾收集器)。
简单来讲,编译器在编译代码时,会自动生成实例的引用计数代码,帮助咱们完成以前MRC须要完成的工做,不过听说除此以外,编译器也会执行某些优化。函数
ARC虽然可以解决大部分的内存泄露问题,可是仍然有些地方是咱们须要注意的。oop
循环引用简单来讲就是两个对象相互强引用了对方
,即retain了对方,从而致使谁也释放不了谁的内存泄露问题。好比声明一个delegate时通常用weak而不能用retain或strong,由于你一旦那么作了,很大可能引发循环引用。测试
这种简单的循环引用只要在coding的过程当中多加注意,通常均可以发现。
解决的办法也很简单,通常是将循环链中的一个强引用改成弱引用就可解决。
另一种block引发的循环引用问题,一般是一些对block原理不太熟悉的开发者不太容易发现的问题。优化
咱们先看看官方文档关于block调用时的解释:Object and Block Variablesui
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:this
- If you access an instance variable by reference, a strong reference is made to self;
- If you access an instance variable by value, a strong reference is made to the variable.
主要有两条规则:第一条规则,若是在block中访问了属性,那么block就会retain住self。
第二条规则,若是在block中访问了一个局部变量,那么block就会对该变量有一个强引用,即retain该局部变量。
url
根据这两条规则,咱们能够知道发生循环引用的状况:
//规则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__block
value to nil to break the retain cycle.
官网提供了几种方案,咱们看看第一种,用__block变量:
在MRC中,__block id x不会retain住x;可是在ARC中,默认是retain住x的,咱们须要
使用__unsafe_unretained __block id x来达到弱引用的效果。
那么解决方案就以下所示:
__block id weakSelf = self; //MRC //__unsafe_unretained __block id weakSelf = self; ARC下面用这个 self.myblock = ^{ [weakSelf doSomething]; NSLog(@"%@", weakSelf.str); };
<!-- ## performSelector的问题 [self performSelector:@selector(foo:) withObject:self.property afterDelay:3]; performSelector延时调用的原理是这样的,执行上面这段函数的时候系统会自动将self.property的`retainCount`加1,直到selector执行完毕以后才会将self.property的`retainCount`减1。这样子若是selector一直未执行的话,self就一直不可以被释放掉,就有可能照成内存泄露。比较好的解决方案是将未执行的perform给取消掉: [NSObject cancelPreviousPerformRequestsWithTarget:self]; 因这种缘由产生的泄露由于并不违反任何规则,是Intrument所没法发现的。 -->
咱们都知道timer用来在将来的某个时刻执行一次或者屡次咱们指定的方法,那么问题来了(固然不是挖掘机)。究竟系统是怎么保证timer触发action的时候,咱们指定的方法是有效的呢?万一receiver无效了呢?
答案很简单,系统会自动retain住其接收者,直到其执行咱们指定的方法。
看看官方的文档吧,也建议你本身写个demo测试一下。
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats
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则不会。
如今问题又来了,看看下面这段代码:
- (void)dealloc { [timer invalidate]; [super dealloc]; }
这个是很容易犯的错误,若是这个timer是个重复性的timer,那么self
对象就会被timerretain
住,这个时候不调用invalidate
的话,self
对象的引用计数会大于1,dealloc
永远不会调用到,这样内存泄露就会发生。
timer都会对它的target进行retain,咱们须要当心对待这个target的生命周期问题,尤为是重复性的timer,同时须要注意在dealloc以前调用invalidate。
关于timer其实有挺多能够研究的,好比其必须在runloop中才有效,好比其时间必定是准的吗?这些因为和本章主题不相关,暂时就不说了。
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay
咱们仍是看看官方文档怎么说的,一样也但愿你们能写个demo验证下。
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来保证延时触发,可是只有在runloop
在default mode
的时候才会执行成功,不然selector
会一直等待run loop
切换到default mode
。
根据咱们以前关于timer
的说法,在这里其实调用performSelector:afterDelay:一样会形成系统对target
强引用,也即retain
住。这样子,若是selector
一直没法执行的话(好比runloop
不是运行在default model
下),这样子一样会形成target
一直没法被释放掉,发生内存泄露。
怎么解决这个问题呢?
其实很简单,咱们在适当的时候取消掉该调用就好了,系统提供了接口:
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget
这个函数能够在dealloc
中调用吗,你们能够本身思考下?
关于NSNotification的addObserver与removeObserver问题
咱们应该会注意到咱们经常会再dealloc
里面调用removeObserver
,会不会上面的问题呢?
答案是否认的,这是由于addObserver
只会创建一个弱引用到接收者,因此不会发生内存泄露的问题。可是咱们须要在dealloc
里面调用removeObserver
,避免通知的时候,对象已经被销毁,这时候会发生crash
.
C 语言不可以调用OC中的retain与release,通常的C 语言接口都提供了release函数(好比CGContextRelease(context c))来管理内存。ARC不会自动调用这些C接口的函数,因此这仍是须要咱们本身来进行管理的.
下面是一段常见的绘制代码,其中就须要本身调用release接口。
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。