前言
在我们OC中, 有一个东西是重中之重的知识点, 那就是内存管理, 什么是内存管理呢? 其实内存管理是我们app在运行的时候所使用的内存大小, 在iOS中, 给我们应用设定了一个固定的内存值, 一旦超过这个值, 系统就会给app发送内存警告, 如果这个警告不处理, 那么就会强制关闭应用, 就是我们所说的闪退, 那么内存有多少种呢? 内存其实是分为五种, 分别为堆, 栈, 全局区, 文字常量区, 程序代码区, 在实际开发中, 我们关注的最多的是堆和栈, 其余三个有兴趣的朋友自行去了解.
开始
在我们没有学习OC内存管理机制的时候, 我们来看一个例子, 看看我们之前所写的代码是怎么样管理内存的:
#import <Foundation/Foundation.h> @interface Person : NSObject @end @implementation Person @end int main() { int a = 1; int b = 2; Person *p = [[Person alloc]init]; return 0; }
示意图
在例子里面, 我们知道a, b, *p都是局部变量, 一旦所在的代码块执行完之后就会消失不见, 但Person对象并不会不见, 而是会一直存在于内存中, 我们都知道OC一开始的时候就会加载所有的类, 给它们分配存储空间, 而这个存储空间就是堆, 那么我们怎么去释放这一块内存空间呢? 其实说到底就是调用一个方法, 给Person这个类发送一条消息, 让它自己释放, 那就可以解决我们的需求.
在OC中, 这种内存管理方式称为计数器, 而存在于堆里面的叫做引用计数, 如果要让堆里面的空间被释放, 唯一的办法就是让引用计数变为0, 这样子系统就会自动回收引用计数为0的类, 其实这样子也就是, 只要引用计数不为0的类, 就会一直存在于内存中, 不会被释放.
那么在什么情况下才会产生引用计数呢? 其实在我们一刚开始创建对象的时候引用计数就会产生, 也就是alloc,new, 还有一个我们暂时没有学到的copy, 所产生的引用计数默认为1, 而这个引用计数的类型是int类型, 大小为4.
谁再去调用, 那么引用计数就会+1, 谁释放就会-1, 以此类推.
PS: 这里的+1就是给对象发送retain消息, 而发送release那么引用计数就会-1, 发送retainCount消息, 就会返回当前引用计数的数值.
那还有没有更加简单直接的方式知道对象被释放了呢, 其实在对象被释放的时候, 系统会自动调用一个叫做dealloc的方法, 而我们可以重写dealloc方法, 就可以知道对象是否有没有被释放.
下面让我们一起来探讨一下吧:
#import <Foundation/Foundation.h> @interface Person : NSObject @end @implementation Person - (void)dealloc { NSLog(@"对象被回收了"); [super dealloc]; } @end int main() { Person *p = [[Person alloc]init]; NSUIntger a = [p retainCount]; NSLog(@"a = %d", a); [p release]; return 0; }
打印出来的结果是:
2015-01-25 11:55:45.619 1.引用计数器的基本操作[2092:163711] a = 1 2015-01-25 11:55:45.620 1.引用计数器的基本操作[2092:163711] 对象被回收了
PS:[super dealloc];这句代码一定要卸载dealloc方法的最后面, 否则就会出错, 原理就是先释放自己, 然后再释放父类.
这里说一下, 其实release并不是释放对象的意思, 而是引用计数减一, 如果你使用了alloc 又使用了retain, 那么引用计数就是2, 写一个release是不能释放的, 必须得有两个, 直到引用计数为0之后才会释放对象, 有增就必须得有减.
那有人会突发奇想, 既然是这样子, 那我就多写几个release吧, 这样也是不对的, 下面让我们来看看:
#import <Foundation/Foundation.h> @interface Person : NSObject @end @implementation Person - (void)dealloc { NSLog(@"对象被回收了"); [super dealloc]; } @end int main() { Person *p = [[Person alloc]init]; NSUIntger a = [p retainCount]; NSLog(@"a = %d", a); [p release]; [p release]; return 0; }
如果是这样子写, 那么程序在运行中就会报错, 报下面这个错误:(一般我们称为野指针错误)
EXC_BAD_ACCESS(code=1, address=0x18)
为什么会报这个错? 其实原理很简单, 在OC中, 一个引用计数变为0的对象, 我们称为僵尸对象, 就是说内存不可用的对象, 而指向僵尸对象的指针我们称为野指针, 当对象被释放掉的时候, 只要指针的地址不清零, 那么指针和对象的关系就不会消失, 所以一旦当我们给一个内存不可用的对象继续release, 那么软件就会崩溃, 而在后面的所有代码都不能运行.
所以在编程的时候, 我们要注意引用计数的增减是否对应, 一旦引用计数不为0, 那么对象就会不被释放, 如果引用计数为0之后还继续release, 那么就会报错, 这个我们需要注意一下.
如果想解决这个问题, 有两种方法, 一种是删掉多余的release, 另一种是把指针变成空指针, 在OC中, 给空指针做操作是不会报错的, 只是会有一个警告而已, 比如:
int main(int argc, const char * argv[]) { Person *p = [[Person alloc]init]; NSUInteger a = [p retainCount]; NSLog(@"a = %ld", a); [p release]; p = nil; [p release]; return 0; }
结果:
2015-01-25 13:40:26.188 1.引用计数器的基本操作[2251:185241] a = 1 2015-01-25 13:40:26.189 1.引用计数器的基本操作[2251:185241] 对象被回收了
还是和我们之前得到的结果一样~~~
总结一下:
1.方法的基本使用
1> retain :计数器+1,会返回对象本身
2> release :计数器-1,没有返回值
3> retainCount :获取当前的计数器
4> dealloc
* 当一个对象要被回收的时候,就会调用
* 一定要调用[super dealloc],这句调用要放在最后面
2.概念
1> 僵尸对象 :所占用内存已经被回收的对象,僵尸对象不能再使用
2> 野指针 :指向僵尸对象(不可用内存)的指针,给野指针发送消息会报错(EXC_BAD_ACCESS)
3> 空指针 :没有指向任何东西的指针(存储的东西是nil、NULL、0),给空指针发送消息不会报错
好了这次我们就讲到这里, 下次我们继续~~~