下面举例说明自动释放池的工作流程:
场景:现在xiaoming和xiaohong都想和小狗一起玩耍,但是他们的需求不一样,他们的玩耍时间不一样,流程如下:
方法一:
// // main.m // TextARC // // Created by taobaichi on 2017/3/27. // Copyright © 2017年 MaChao. All rights reserved. // #import <Foundation/Foundation.h> #import "Dog.h" int main(int argc, const char * argv[]) { //创建一个自动释放池 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc]init]; //模拟:宠物中心派出小狗 Dog * dog = [[Dog alloc]init]; //模拟:xiaoming需要和小狗玩耍,需要将其引用计数加1 [dog retain]; NSLog(@"xiaoming和小狗玩耍,小狗的引用计数为 %ld",dog.retainCount); //模拟:xiaohong需要和小狗玩耍,需要将其引用计数加1 [dog retain]; NSLog(@"xiaohong和小狗玩耍,小狗的引用技术为 %ld",dog.retainCount); //模拟:xiaoming确定不想和小狗玩耍了,需要将其引用计数减1 [dog release]; NSLog(@"xiaoming确定不想和小狗玩了,小狗的引用计数:%ld",dog.retainCount); //模拟:xiaohong不确定何时不想和小狗玩耍了,将其设置为自动释放 [dog autorelease]; NSLog(@"加入自动释放池,小狗的引用计数: %ld",dog.retainCount); [dog release]; NSLog(@"释放池子"); [pool release]; return 0; }
方法二:
// // main.m // TextARC // // Created by taobaichi on 2017/3/27. // Copyright © 2017年 MaChao. All rights reserved. // #import <Foundation/Foundation.h> #import "Dog.h" int main(int argc, const char * argv[]) { @autoreleasepool { //模拟:宠物中心派出小狗 Dog * dog = [[Dog alloc]init]; //模拟:xiaoming需要和小狗玩耍,需要将其引用计数加1 [dog retain]; NSLog(@"xiaoming需要和小狗玩耍,小狗的引用计数为 %ld",dog.retainCount); //模拟:xiaohong需要和小狗玩耍,需要将其引用计数加1 [dog retain]; NSLog(@"xiaohong需要和小狗玩耍,小狗的引用计数: %ld",dog.retainCount); //模拟:xiaoming确定不想和小狗玩耍了,需要将其引用技术减1 [dog release]; NSLog(@"xiaoming确定不想和小狗玩耍了,小狗的引用计数: %ld",dog.retainCount); //模拟:xiaohong不确定何时不想和小狗玩耍了,将其设置为自动释放 [dog autorelease]; NSLog(@"加入自动释放池,小狗的引用计数: %ld",dog.retainCount); //没人需要和小狗玩耍了,将其引用技术减1 [dog release]; NSLog(@"释放池子"); } return 0; }
输出结果如下(两种方法输出结果完全一致):
2017-03-27 14:10:40.808032 TextARC[3643:121392] 小狗被派出去啦! 初始引用计数为 1 2017-03-27 14:10:40.808204 TextARC[3643:121392] xiaoming和小狗玩耍,小狗的引用计数为 2 2017-03-27 14:10:40.808227 TextARC[3643:121392] xiaohong和小狗玩耍,小狗的引用技术为 3 2017-03-27 14:10:40.808246 TextARC[3643:121392] xiaoming不想和小狗玩了,小狗的引用计数:2 2017-03-27 14:10:40.808269 TextARC[3643:121392] 加入自动释放池,小狗的引用计数: 2 2017-03-27 14:10:40.808291 TextARC[3643:121392] 释放池子 2017-03-27 14:10:40.808339 TextARC[3643:121392] 小狗回到宠物中心
可以看到,当池子释放后,dog对象才被释放,因此在池子释放之前,xiaohong都可以尽情地和小狗玩耍
使用自动释放池需要注意:
- 自动释放池实质上只是在释放的时候给池子中所有对象发送
release
消息,不保证对象一定会销毁,如果自动释放池向对象发送release
消息后对象的引用计数仍大于1,对象就无法销毁。
- 自动释放池中的对象会集中 在同一时间释放,如果操作需要生成的对象较多占用内存空间大,可以使用多个释放池来进行优化,比如在一个循环中需要创建大量的临时变量,可以创建内部的池子降低占用内存峰值。
-autorelease
不会改变对象的引用计数
自动释放池的常见问题:
在管理对象释放的问题上,自动释放池帮助我们节省了大量的时间,但是有时候它却未必会达到我们期望的效果,比如在一个循环事件中,如果循环次数较大或者事件处理占用内存较大,就会导致内存占用不断增长,可能会导致不希望看到的后果。
示例代码:
for (int i = 0; i < 100000; i++) { NSString * log = [NSString stringWithFormat:@"%d",i]; NSLog(@"%@",log); }
前面讲过,自动释放池的释放时间是确定的,这个例子中自动释放池会在循环事件结束时释放,那么问题来了:在这个十万次的循环中,每次都会生成一个字符串并打印,这些字符串对象都放在池子中直到循环结束才会释放,因此在循环期间内存不增长。
这类问题的解决方案是在循环中创建新的自动释放池,多少个就你和释放一次由我们自行决定。
for (int i = 0; i < 100000; i++) { @autoreleasepool { NSString * log = [NSString stringWithFormat:@"%d",i]; NSLog(@"%@",log); } }
3. iOS的内存管理规则
3.1 基本原则
- 当你通过
new
、alloc
或copy
方法创建一个对象时,它的引用计数为1,当不再使用该对象时应该向对象发送release或者autorelease消息释放对象。
- 当你通过其他方法获得一个对象时,如果对象引用计数为1且被设置为
autorelease
,则不需要执行任何释放对象的操作
- 如果你打算取得对象所有权,就需要保留对象并在操作完成之后释放,且必须保证
retain
和release
次数对等
应用到文章开头的例子中,小朋友每申请一个小狗(生成对象),最后都要归还到宠物中心(释放对象),如果只申请而不归还(对象创建了没有释放),那宠物中心的小狗就会越来越少(可用内存越来越少),到最后一个小狗都没有了(内存被耗尽),其他小朋友就再也没有小狗可申请了(无资源可使用),因此,必须要遵守规则:申请必须归还(规则1),申请几个必须归还几个(规则3),如果小狗被设定归还时间则不用小朋友主动归还(规则2)。
3.2 ARC
在MRC时代,必须严格遵守以上规则,否则内存问题将成为恶魔一样的存在,然而来到ARC时代,事情似乎变得轻松了,不用再写无止尽的retain
和release
似乎让开发变得轻松,对初学者变得更友好。
ObjC2.0引入了垃圾回收机制
,然而由于垃圾回收机制会对移动设备产生某些不好的影响(例如由于垃圾清理造成的卡顿),iOS并不支持这个机制,苹果的解决方案就是ARC(自动引用计数)。
iOS5 以后,我们可以开启ARC模式,ARC可以理解成一位管家,这个管家会帮我们向对象发送retain
和release
语句,不再需要我们手动添加了,我们可以更舒心地创建或引用对象,简化内存管理步骤,节省大量的开发时间。
实际上,ARC并不是垃圾回收,也并不是不需要内存管理了,它是隐式的内存管理,编译器在编译的时候会在代码插入合适的retain
和release
语句,相当于在背后帮我们完成了内存管理的工作。
注意:
- 如果你的工程历史比较悠久,可以将其从MRC转换成ARC,跟上时代的步伐更好的维护
- 如果你的工程引用了某些不支持ARC的库,可以在
Build Phases
的Compile Sources
将对应的.m文件的编译器参数配置为-fno-objc-arc
- ARC能帮我们简化内存管理,但不代表它是万能的,还是有它不能处理的情况,这些需要我们手动处理,比如循环引用、非ObjC对象、
Core Foundation
中的malloc()
或者free()
等等
思考:MRC有什么缺点,ARC有什么局限性?
3.3 ARC的修饰符
ARC提供四种修饰符,分别是strong
、weak
、autoreleasing
、unsafe_unretained
。
__strong
:强引用,持有所指向对象的所有权,无修饰符情况下的默认值。如需强制释放,可置nil。
比如我们常用的定时器
NSTimer * timer = [NSTimer timerWit...];
相当于
NSTimer * __strong timer = [NSTimer timerWith...];
当不需要使用的时候,强制销毁定时器
[timer invalidate]; timer = nil;
__weak
:弱引用,不持有所指向对象的所有权,引用指向的对象内存被回收之后,引用本身会置nil,避免野指针.
比如避免循环引用的弱引用声明:
__weak typeof(self)weakSelf = self;
__autoreleasing
:自动释放对象的引用,一般用于传递参数
比如一个读取数据的方法
-(void)loadData:(NSError **)error
当你调用的时候会发现这样的提示
NSError * error; [self loadData:(NSError *__autoreleasing *)]
这是编译器自动帮我们插入以下代码
NSError * error; NSError * __autoreleasing temErr = error; [self loadData:&tmpErr];
__unsafe_unretained
:为兼容iOS5以下版本的产物,可以理解成MRC下的weak,现在基本用不到,这里不做描述.