如果你在 ARC 下感觉 @autoreleasepool 是个鸡肋,那就错错啦 !!!
@autoreleasepool还是有用滴。
1.ARC下用 @autoreleasepool { }主要还是为了避免内存峰值。简单点的说在{ }中存放的对象会在稍后的某个时刻释放掉
正常情况下,你创建的变量会在超出其作用域的时候被释放掉。
而如果你的函数写的很长,在你函数运行过程中出现很多中间变量,占据了大量的内存,怎么办?
用@autoreleasepool。
在@autoreleasepool中创建的变量,会在@autoreleasepool结束的时候执行一次release,进行释放。其实@autoreleasepool就相当于一层作用域。
(1)在自动释放池@autoreleasepool{}中alloc一个对象后(如p1),仍然需要用[p1 autorelease];只是这个语句和[p1 release];不同,后者表示把p1的retainCount-1,而前者仅仅表示把p1放到自动释放池中返回一个self,自动释放池结束销毁时,统一对里面的对象引用计数retainCount-1。(2)@autoreleasepool{}可以随意创建,也可以嵌套使用。(3)不管这个对象是在自动释放池内还是外创建的,只要在自动释放池内写一个[p1 autorelease];p1就会被放到自动释放池中。注意autorelease是一个方法,且只有在自动释放池中使用才有效。(4)如果把一个对象重复加到自动释放池如[p1 autorelease];[p1 autorelease];,那么会出错。原因是:加载几次,届时自动释放池就会用[p1 release];释放几次,但是由于这两个加载的对象其实是一个对象同样地址,所以第一次自动释放正确,第二次自动释放时发现已经被释放了,所以p1就变成了野指针。(5)以下是自动释放池嵌套的使用规则和注意点。#import#import "Person.h"
int main(int argc, const char * argv[]) {
Person *p1=[[Person alloc]init];
@autoreleasepool {
@autoreleasepool {
[p1 autorelease];
}//在执行到此处时,p1被自动释放
}
//以下代码有错误
@autoreleasepool {
[p1 autorelease];//此时p1被加入进来
@autoreleasepool {
[p1 autorelease];//被重复加载进来,但仍然同一个
}//此处,p1被自动释放了,所以第一次加进来的那个也被释放了,因为是同一个对象
}//所以此处在调用[p1 release];时就出现报错:野指针
return 0;
}
(6)@autoreleasepool的应用:如果需要在方法中创建对象,并把这个对象作为返回值,那么可以在这个方法中使用[*** autorelease];把它加入到自动释放池中,否则,直接用[*** release];来匹配alloc的话,在该方法中就已经把这个对象alloc和release了一遍相当于释放了,那么所谓的返回对象返回的时一个野指针(没有指向任何对象)。当然,调用这个方法的代码页需要写在自动释放池作用域内才生效。
(7)接上面。返回对象的那个方法中,创建对象不建议直接用类名,而是用self,否则如果存在子类调用会崩溃。如Car *car1=[[self alloc]init];
(8)其实诸如NSString *str1=[NSString stringWithFormat:@"%@",@"hello"];也是调用了一个方法,并且返回了一个字符串对象。比照(6)和(7)我们得知这个stringWithFormat应该也是顺便返回了一个autorelease。
(9)在ARC机制中,我们用@property声明的成员变量,建议用strong代替之前手动管理内存时的retain,虽然后者仍然可以使用。因为我们在ARC中内存管理就是看是否有强指针指向对象,如有就不回收,如没有就回收。所以强指针是strong,相反是weak。而基本数据类型我们还是习惯用assign。
(10)虽然Xcode提供了非ARC转换成ARC的,很少有把整个非ARC转换成ARC的。如果我们导入第三方库时,需要非ARC和ARC共存,即我们系统默认是ARC,我们需要让系统不要去管这个非ARC的第三方库,如下设置:双击响应的.m文件,输入-fno-objc-arc回车即可。
(11)顺便,当出现两个类循环引用的话(也就是A要包含B,B要包含A,即A对象要作为B的变量,B对象要作为A的变量),只需要把一方的strong改成weak,并且在响应的.h文件中把#import ".h"改成Class ***。如果因为改成Class ***而无法使用那个类的方法的话,只需要在它的.m文件中#import“.h”文件即可,这个因为不是在.h文件中导入所以不冲突。
注意:利用webview加载gif图时,左右来回滑动导致内存暴涨,用了
@autoreleasepool {
for (UIView *subview in self.welcomeScr.subviews) {
if ([[subview class]isSubclassOfClass:[UIWebView class]]) {
[subview removeFromSuperview];
}
}
NSInteger index= self.welcomeScr.contentOffset.x/ScreenW;
NSInteger count = index+1;
NSString *path = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"%ld",index+1] ofType:@"gif"];
NSData *gifData = [NSData dataWithContentsOfFile:path];
UIWebView *webView = [self createWebViewWithFrame:CGRectMake(ScreenW * index ,0,ScreenW,ScreenH)];
[webView loadData:gifData MIMEType:@"image/gif" textEncodingName:nil baseURL:nil];
[self.welcomeScr addSubview:webView];
}
之后,内存有些变小,但是减下来的内存只有1~2MB,没有比增长的多,更没有完全释放掉
通常情况下我们是不需要手动创建 Autorelease Pool ,但以下三种情况是例外的[5]:
编写的程序不基于 UI 框架,如命令行程序
在循环中创建大量临时对象
在主线程之外创建新的线程,在新线程开始执行处,创建自己的 Autorelease Pool ,否则将导致内存泄漏。
根据Apple的文档,@autoreleasepool使用场景如下:
写基于命令行的的程序时,就是没有UI框架,如AppKit等Cocoa框架时。
写循环,循环里面包含了大量临时创建的对象。(本文的例子)
创建了新的线程。(非Cocoa程序创建线程时才需要)
长时间在后台运行的任务。
对于每一个Runloop(或者称为事件周期), 系统会隐式创建一个Autorelease pool(自然会有多个Autorelease pool),这样所有的release pool会构成一个象CallStack一样的一个栈式结构,在每一个Runloop结束时,当前栈顶的Autorelease pool会被销毁,这样这个pool里的每个Object会被release。那什么是一个Runloop呢? 一个UI事件,Timer call, delegate call, 都会是一个新的Runloop。
文/dacheng(简书作者)
原文链接:http://www.jianshu.com/p/53de8a66b91a
文/Hran233(简书作者)
原文链接:http://www.jianshu.com/p/2595558e04ff
http://www.jianshu.com/p/494481d59278