在 iOS 5/ Mac OS X 10.7 开始导入ARC(自动引用计数)机制,利用 Xcode4.2 可以使用,所以我们现在开发时大多都会依赖ARC来管理内存,确实省去了很多手动管理内存的麻烦,重要的是但是!
但是,ObjC中的内存管理机制跟C语言中指针的内容是同样重要的,要开发一个程序并不难,但是优秀的程序则更测重于内存管理,它们往往占用内存更少,运行更加流畅。如果你不了解iOS管理内存的机制,只会用ARC让系统帮你管理内存,可以说你的知识结构是有缺陷的,在解决一些程序中遇到的bug时会浪费掉大量的时间。所以学习MRC(手动管理内存)来深刻理解iOS内存管理机制还是很有必要的。
这次就来谈谈这看似过时的MRC。
看这篇文章时,你可能需要Xcode关闭ARC。
XCode关闭ARC,在Xcode中关闭ARC:项目属性—Build Settings–搜索“garbage”找到Objective-C Automatic Reference Counting设置为No即可。
如果需要对特定文件开启或关闭ARC,可以在工程选项中选择Targets -> Build Phases -> Compile Sources,在里面找到对应文件,添加flag:
打开ARC:-fobjc-arc
关闭ARC:-fno-objc-arc
ObjC中内存是如何管理的?
ObjC中内存的管理是依赖对象引用计数器(在ObjC中每个对象内部都有一个与之对应的整数retainCount)来进行的,当一个对象在创建(通过alloc,new、copy来创建)之后它的引用计数器为1,当调用这个对象的retain方法之后引用计数器自动在原来的基础上加1,当调用这个对象的release方法之后它的引用计数器减1,如果一个对象的引用计数器为0,则系统会自动调用这个对象的dealloc方法来销毁这个对象。
Objective-C的对象生成于堆之上,生成之后,需要一个指针来指向它。(注意基本类型是由系统自己管理的,放在栈上)
alloc:为一个新对象分配内存,并且它的引用计数为1。调用alloc方法,你便有对新对象的所有权
copy:制造一个对象的副本(克隆体),该副本的引用计数为1,调用者具有对副本的所有权
new:[className new]基本等同于[[className alloc] init];
注意:
retainCount为0的时候才会自动调用dealloc方法,所以可以通过dealloc方法来查看是否一个对象已经被回收,如果没有被回收则有可能造成内存泄露。
如果一个对象被释放之后,那么最后引用它的变量我们手动设置为nil,否则可能造成野指针错误,而且需要注意在ObjC中给空对象发送消息是不会引起错误的。
野指针错误形式在Xcode中通常表现为:
1 | Thread 1:EXC_BAD_ACCESS(code=EXC_I386_GPFLT)错误。因为你访问了一块已经不属于你的内存。 |
如何管理,写代码经验?(内存释放的原则)
谁创建alloc,谁释放,即在一个作用域内(两个大括号包起来的范围),如果有创建(alloc,new、copy),最后在右括号之前都必须release。
那什么时候会隐含用到retain,release方法呢??
@property声明了属性a,那用b给a赋值(someObject.a=b)时就需要考虑b和a的retainCount。
影响retainCount的@property参数有assign,retain,copy。默认为assign。
assign
- 直接赋值,不影响retainCount
- 用于基本数据类型
retain
- [a release] [b retain]
- 用于NSObject的子类对象。
copy
- [a release],但是建立一个索引计数为1的对象,这个对象和b一样,b.retainCount不变
- 用于NSstring。
注意,MRC只有属性标识符,没有变量标识符。变量需要retain或者copy其他变量时,直接调用其retain、copy方法。
自动释放池
自动内存释放使用@autoreleasepool关键字声明一个代码块,如果一个对象调用了autorelase方法,那么当代码块执行完之后,在块中调用过autorelease方法的对象都会自动调用一次release方法。这样一来就起到了自动释放的作用,同时对象的销毁过程也得到了延迟(统一调用release方法)。看下面的代码:
这部分代码来自这里
Person.h
1 | //Person.h |
Person.m
1 | //Person.m |
main.m
1 | #import <Foundation/Foundation.h> |
当上面@autoreleaespool代码块执行完之后,三个对象都得到了释放,但是person4并没有释放,原因就不细说了。
通过上面的例子主要想说明一个问题(关于类方法创建对象):
在ObjC中通常如果一个静态方法返回一个对象本身的话,在静态方法中我们需要调用autorelease方法,因为按照内存释放原则,在外部使用时不会进行alloc操作也就不需要再调用release或者autorelase,所以这个操作需要放到静态方法内部完成。
对于自动内存释放简单总结一下:
- autorelease方法不会改变对象的引用计数器,只是将这个对象放到自动释放池中;
- 自动释放池实质是当自动释放池销毁后调用对象的release方法,不一定就能销毁对象(例如如果一个对象的引用计数器>1则此时就无法销毁,例子中的person4);
- 由于自动释放池最后统一销毁对象,因此如果一个操作比较占用内存(对象比较多或者对象占用资源比较多),最好不要放到自动释放池或者考虑放到多个自动释放池;
- ObjC中类库中的静态方法一般都不需要手动释放,内部已经调用了autorelease方法;所以静态方法命名时都不要以alloc和new开头,容易误解。
后话
下次会讨论ARC。