为什么要注意内存管理
无论编写任何程序, 都需要有效和高效地管理资源。内存资源则是重中之重, 都知道如果电脑内存如果不够用, 系统就能会卡, 反应尺寸, 那么手机也是一样的。在OC程序中对象一旦不需要了必须释放资源.
在一个复杂的系统中,可能很难精确地确定从何时起您不再需要某个对象。Cocoa定义了一些有助于使这一抉择变得更加容易的规则 和原则
非ARC(iOS5之前)
内存管理原则 :
如果使用alloc、new、copy、mutableCopy创建一个对象, 系统会给这个对象的内部引用计数器赋值1, 您也就拥有这个对象的所有权, 不是自己创建的也可以对对象进行retain操作也会拥有这个对象的所有权。注意如果您对对象retain操作, 其内部的引用计数器也会 +1操作
释放对象的所有权: 可以用release, 或者autorelease自动释放一个对象的所有权. 注意: 不一定release之后对象就一定被释放了, 他只是释放对象的所有权, 换句话来说只是将其内部的引用计数器 -1操作, 对象是否被释放要看内部的【引用计数器】为0
autorelease不是马上释放对象, 而是在一个运行循环(runloop)结束之后向所有等待释放的对象发送一条release操作, 详细介绍在后面
对象的所有权(引用计数器):
说简单点就是如果你引用给一个对象就拥有这个对象的所有权, 一个对象可以被N个所有者拥有, 如果有人拥有这个对象, 那么这个对象就一直存在, 如果没有人拥有就会在运行时被释放. Cocoa 制定了一个非常完美的策略来实现对象是否被释放
- 自己使用alloc, new, copy, mutableCopy创建的对象就有对象的所有权, 并且其内部的引用计数器retainCount=1。
- 如果不是自己创建的您可以用retain来获取对象的拥有权, retain之后, 对象其内部的retainCount+1操作
- 如果不使用对象则需要对对象进行release操作, 其内部retainCount-1, 您也可以使用autorelease自动释放对象
- 如果类被销毁之前需要在dealloc方法里对其内部的所有属性进行release操作, 对象销毁同时对其内部对象所有权也就释放了
- 需要注意非ARC如果调用dealloc 必须重写父类方法, 重写代码一定放在最后调用, 如果先调用该对象就成为僵尸对象, 僵尸对象是不可操控对象, 从而无法释放内部对象
下面我们看看具体代码
- 以new为例
如果您在ARC项目中把一个class修改成非ARC状态之后在执行44行的时候会发现跑通了没报错, 而且他的retainCount值也是1, 首先解决这个现象需要在product-> scheme-> Edit scheme -> Diagnostics -> Enable Zombie Objects勾选上, 在运行就会出错了, 那么这里给你详细的解释是:
事实上obj的确已经被dealloc了,保留计数器的值也已经变成0了,其原来占用的内存也已经不可用了,但是原来这块内存中的内容还没有变(标记删除),将会在未来某个不确定的时间上被清理(就是runloop做的事),这就是为什么NSLog输出的obj保留计数器的值仍为1,而如果在此时再加上一个NSLog, 用1个僵尸对象调用一块被释放的坏内存, 于是程序crash
- 不是自己创建的对象
首先定义一个Student创建一个对象叫car
重写 car对象setter方法, 里面判断是否是同一个对象传进来, 如果不同对象需要对其旧对象释放, 新对象retain操作, 这样就拥有对象的所有权
下面是对象的retainCount值, 如果看不太明白自己动手操作一下就明白了.
引用计数器(保留数)
每一个对象内部都有一个retainCount属性, 用来判断有多少个对象拥有它, 如果没有对象拥有它就被释放
- 当对象被创建(retainCount =1) 引用计数器为1
- 当向对象发送retain消息时, 该对象引用计数器会+1
- 当向对象发送release消息时, 该对象引用计数器会-1, 如果发送autorelease 不会立即-1 是在运行时某一时间-1
- 引用计数器为0 就会被释放掉
自动释放(autorelease), 有人也叫半自动释放
如果向一个对象发送autoreleace消息之后, 不会立即被释放, 对象会被放入自动释放池中, 等待系统会在某一时间点, 对其释放池内部的所有对象进行一次releace操作
自动释放池
自动释放池是一个NSAutoreleasePool实例(允许被嵌套使用), 其中“包含”已经收到autorelease消息的其他对象; 当自动释放池被回收时, 它会向其中的每个对象发送一条release消息。一个对象可以被数次放入一个自动释放池中, 并且在每次被放入池中的时候都会收到一条release消息。因此,向对象发送autorelease消息(而不是release消息)可以至少将该对象的生命周期延长至自动释放池本身 被释放的时候(如果在此期间对象被保留,则它可以存活更久)。 多了不详细介绍, 现在都ARC时代了, 谁还用非ARC东西, 如果不研究底层东西可以忽略
5.0之前的写法:
5.0之后写法
循环引用
项目中经常出现的就是2个对象互相引用的情况, 如果2端全用retain 进行修饰, 就会出现循环引用状况, 因为2者互相引用导致计数器都不为0, 2者谁也不能放过谁, 长时间就出现app crash
代码表示如下:
Car.h文件
Car.m文件
Student.h文件
Student.m文件
实例代码:
看log最后他们的引用计数都为1, 解决办法: 一端用assign, 一段用retain 即可
拿Car文件举例, Student文件没动
Car.h文件修改为
Car.m文件修改为
实例代码:
ARC (5.0之后)
iOS5出来一个非常好的机制就是ARC, 他不用程序员手动去管理内存, 完全由系统自动管理, 系统也屏蔽了release, retain, retainCount方法, dealloc里也不能调用[super dealloc], 因为ARC的内存管理原则与非ARC完全不同。下面就看一下ARC内存管理原则
ARC内存管理原则: 对象是否有强指针指向(引用), 有强指针引用对象一直存在, 没有就会被释放.
ARC出来之后出来2个关键字:weak, strong(弱, 强), 干什么用的? 修饰对象用的
weak: 用来修饰弱指针引用。
strong: 用来修饰强指针引用。(默认: 系统默认创建出来的对象都是强指针引用)
有人会问assign不也可以修饰对象吗? weak和assign有什么区别?
什么时候用weak:一般在解决循环引用的时候用weak,比如delegate, block内部需要引用外部对象, 或者该对象已经被强引用着, 而现在需要转成weak.
什么时候用assign:在MRC中解决循环引用的时候, 修饰基本数据类型, 修饰对象是简单的赋值, 不操作内部的引用计数器, 而ARC中它不能修饰对象, 只能修饰基本数据类型.
ARC的循环引用
MRC中有循环引用在ARC中也同样有循环引用,为什么会造成循环引用?
就是因为2个对象都属于强类型, 我们看看下面的图片你就应该明白了
学生引用车, 车反过来引用学生, 导致2者都不能被释放, 互相咬着, 谁也不放谁
解决办法: 一端用weak, 一端用strong, 让其一端变成弱引用.看看下面图片
这样c对象内部是s 被释放了,s里的car就不存在了, c没有强指针引用, 所以c也就被释放了
ARC与MRC的性能对比:
ARC的性能是更好的,这主要得益于一些底层的优化以及autorelease pool的优化,这个从官方文档也能看到。
但在一些情况下,ARC确实是更慢,ARC会发送一些额外的retain/release消息,如一些涉及到临时变量的地方, 之所以添加这些额外的retain/release操作,是为了保证代码运行的正确性
最后补充一下ARC 与MRC的转换
-
MRC旧项目改 ARC新项目
edit —> refactor —> convert to ARC
证明是不是ARC项目
build settings 搜索auto —> objective - c ARC
-
ARC ,MRC共存
build phases 双击文件/回车 输入 -fno-objc-arc 非ARC -f-objc-arc ARC
如果有什么问题欢迎留言让我们一起进步........欢迎您耐心看完。