autoreleasepool自动释放池
自动释放池始于MRC时代,主要是用于 自动 对 释放池内 对象 进行引用计数-1的操作,即自动执行release方法。
在MRC中使用autoreleasepool必须在代码块内部手动为对象调用autorelease把对象加入到的自动释放池,系统会自动在代码块结束后,对加入自动释放池中的对象发送一个release消息。无需手动调用release
int main(int argc, const char * argv[])
{
Person *father = [[Person alloc] init];//引用计数为1
@autoreleasepool {//这里创建自动释放池
int a = 10; // a在栈,10在常量区
int b = 20; //b在栈,20在常量区
// p : 栈
// [[Person alloc] init]创建的对象(引用计数器==1) : 在堆
Person *p = [[Person alloc] init];
//MRC下需要手动让对象加入自动释放池
[p autorelease];
Person *pFather = father;
[father retain];//father指针指向的对象被pFather引用,在MRC下需要手动让被引用对象引用计数+1
NSLog(@"pFather对象内存地址:%p,pFather的引用计数:%zd",pFather,[pFather retainCount]);
NSLog(@"father对象内存地址:%p,father的引用计数:%zd",father,[father retainCount]);
}//这里释放 自动释放池
// 当autoreleasepool内部代码块执行完毕后上面代码块后, 栈里面的变量a、b、p 都会被回收
// 但是堆里面的Person对象还会留在内存中,因为它是计数器依然是1。当autoreleasepool代码块执行完毕后,会对释放池内部的所有对象执行一个release消息。如果发送release消息后,对象引用计数为0了,那么就会被系统回收。
NSLog(@"father对象内存地址:%p,father的引用计数:%zd",father,[father retainCount]);
return 0;
}
在ARC中对@autoreleasepool的使用相比MRC不太多。主要用于一些大内存消耗对象的重复创建时,保证内存处于比较优越的状态。常用于创建对象较多的for循环中。在ARC下不要手动的为@autoreleasepool代码块内部对象添加autorelease,ARC下自动的把@autoreleasepool代码块中创建的对象加入了自动释放池中。
for (int i = 0; i < 10000000; i++)
{
@autoreleasepool{
NSMutableArray *array = [NSMutableArray new];
NSMutableDictionary *dic = [NSMutableDictionary new];
NSMutableArray *array1 = [NSMutableArray new];
NSMutableDictionary *dic1 = [NSMutableDictionary new];
NSMutableArray *array2 = [NSMutableArray new];
NSMutableDictionary *dic2 = [NSMutableDictionary new];
NSData *data = UIImageJPEGRepresentation([UIImage imageNamed:@"testimage"], 1);
NSError *error;
NSURL *url = [NSURL URLWithString:@"www.baidu.com"];
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding
error:&error];
}
}
可以从上图看出,使用autoreleasepool时,内存一直保持稳定状态,上次几乎没浮动
因为没有使用 autoreleasepool,随着for循环一直执行下去,内存一直在上升。
autorelease对象释放的时机
1.系统自行创建的@autoreleasepool释放时机
线程与Runloop是一对一关系,主线程中会自动创建Runloop,而子线程需要自行调用Runloop来让子线程自动创建Runloop。当@autoreleasepool加入到某个线程时,该线程的Runloop会
借用runloop的Autorelease对象释放的背后的解释(ARC环境下)
图中第1步 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前
图中第6步 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
图中第10 Observer 监视事件是exit(即讲退出runloop),其回调内会调用 _objc_autoreleasePoolpop() 释放自动释放池。
从上面就能看出,Runloop中系统自动创建的@autoreleasepool是在准备进入休眠状态才被销毁的。所以在ARC下,在线程中的临时对象是在当前线程的Runloop进入休眠或者退出loop或者退出线程时被执行release的。
2.自己创建的@autoreleasepool
@autoreleasepool
{//这个{开始创建的自动释放池,这里开始内部的对象自动加入autorelease
}//这个}开始,不必再对 对象加入autorelease,自动释放池被销毁。
从上面就能看出,自行创建的@autoreleasepool,是在}后被释放的,而其中的autorelease对象,也是在这个时候自动执行的release操作。
从 1
, 2
两点可以看出,虽然autorelease对象释放的时机并不都是在代码块结束后就释放。但是他们有一个共同特性,那就是必定是在@autoreleasepool被销毁时,释放的。 所以要清楚autorelease对象什么时候被释放,只需要搞清楚@autoreleasepool什么时候被销毁即可
主线程中既有系统创建的@autoreleasepool也有开发者自行创建的@autoreleasepool。那么他的释放顺序是怎样的呢?
因为@autoreleasepool是以栈的形式存储的,按照先进后出的规则释放栈中每个@autoreleasepool。主线程的@autoreleasepool是在Runloop一开始就创建了所以,它必然是栈最里面的,而自行创建的@autoreleasepool是在Runloop运行中创建的,所以在栈上面一点。按照栈的规则,@autoreleasepool是先释放自行创建的@autoreleasepool,在释放系统创建的。
NSString 单独说
为什么要单独说NSString呢,因为NSString在内存上与其他类型存在很大的不同
//该代码是在MRC环境下测试用
NSString *str1 = @"123456789";//用@""方法创建一个 固定长度为9的字符串
NSString *str2 = @"1234567890";//用@""方法创建一个 固定长度为10的字符串
NSString *str3 = [NSString stringWithFormat:@"234567890"];//用stringWithFormat方法创建一个 固定长度为9的字符串
NSString *str4 = [NSString stringWithFormat:@"2345678901"];//用stringWithFormat方法创建一个 固定长度为10的字符串
NSString *str5 = [[NSString alloc] initWithString:@"345678901"];//用initWithString方法创建一个 固定长度为9的字符串
NSString *str6 = [[NSString alloc] initWithString:@"3456789012"];//用initWithString方法创建一个 固定长度为9的字符串
NSString *str7 = [[NSString alloc] initWithFormat:@"456789012"];//用initWithFormat方法创建一个 固定长度为9的字符串
NSString *str8 = [[NSString alloc] initWithFormat:@"4567890123"];//用initWithFormat方法创建一个 固定长度为9的字符串
NSString *str9 = [NSString stringWithFormat:@"1234567890"];//用stringWithFormat方法创建一个 固定长度为10的字符串并与str2字符串一样的字符串
NSString *str10 = [[NSString alloc] initWithString:@"1234567890"];//用initWithString方法创建一个 固定长度为10的字符串并与str2字符串一样的字符串
NSLog(@"str1 用@"" 的retainCount为:%ld \n 对象内地地址:%p",[str1 retainCount],str1);
NSLog(@"str2 用@"" 的retainCount为:%ld \n 对象内地地址:%p",[str2 retainCount],str2);
NSLog(@"-----------------------------------------------------------------");
NSLog(@"str3 用stringWithFormat 的retainCount为:%ld \n 对象内地地址:%p",[str3 retainCount],str3);
NSLog(@"str4 用stringWithFormat 的retainCount为:%ld \n 对象内地地址:%p",[str4 retainCount],str4);
NSLog(@"-----------------------------------------------------------------");
NSLog(@"str5 用initWithString 的retainCount为:%ld \n 对象内地地址:%p",[str5 retainCount],str5);
NSLog(@"str6 用initWithString 的retainCount为:%ld \n 对象内地地址:%p",[str6 retainCount],str6);
NSLog(@"-----------------------------------------------------------------");
NSLog(@"str7 用initWithFormat 的retainCount为:%ld \n 对象内地地址:%p",[str7 retainCount],str7);
NSLog(@"str8 用initWithFormat 的retainCount为:%ld \n 对象内地地址:%p",[str8 retainCount],str8);
NSLog(@"-----------------------------------------------------------------");
NSLog(@"使用lu看一下 str1 的retainCount为:%lu \n 对象内地地址:%p",[str1 retainCount],str1);
NSLog(@"使用lu看一下 str4 的retainCount为:%lu \n 对象内地地址:%p",[str4 retainCount],str4);
NSLog(@"-----------------------------------------------------------------");
NSLog(@"str9 字符串与str2一样 的retainCount为:%lu \n 对象内地地址:%p",[str9 retainCount],str9);
NSLog(@"str10 字符串与str2一样 的retainCount为:%lu \n 对象内地地址:%p",[str10 retainCount],str10);
输出结果
1.为什么用 @""
, stringWithFormat
, initWithString
, initWithFormat
四种方式。
因为方式不同,创建的对象所存在内存区域不同。你会发现str2 与 str9 字符串内容一样。为什么对象地址一个是0x1053674f8
另一个是0x604000033580
。正常情况下,字符串内容一样,应该取的是同一个内存地址。就像str2与str10一样。虽然创建的方法不一样,但是字符串一样,内存地址就是一样的。 这就是区别。 @“”与initWithString方法创建的字符串于stringWithFormat、initWithFormat创建的字符串所在的内存区域不同。
2.为什么要区分长度9 和 长度10的字符串?
因为字符串长度不一样,字符串所在内存区域不同,retainCount也不同。从上面结果中可以看出,str3和str4是同一种方式创建的字符串,但一个内存是0xa287dcaecc2ac5d9
一个是0x6040000339a0
。因为前者是在五大区域之外的内存区,而后者在堆中。
由上面连2点结合可知,由initWithString和stringWithString创建的NSString对象,不管字符串的内容和长度怎么变化,该字符串对象始终是存储在常量区的,引用计数为-1;从用%lu打印来看initWithString和stringWithString创建的字符串retainCount是无符号长整型的最大值。所以可以说他们没有引用计数这个概念
而由initWithFormat和stringWithFormat创建的对象,如果字符串内容是非汉字的,那么当字符串长度小于10个时,该字符串存储区域在五大区域之外,且随着字符串长度的变化,存储地址会有很大变化。当字符串长度超过10个以后,该字符串在堆中,与正常的OC对象一样。这里为什么要说非汉字呢,因为如果字符串内容是汉字,不管字符串的内容和长度怎么变化,该字符串都是在堆中,与正常OC对象一样。