- 为什么要内存管理:一般设备的内存的大小是有限的,当有些数据不再被使用了,我们就应该及时回收相应的内存空间,这样就有利于内存资源的合理使用。
- OC中哪些数据需要手动释放呢?任何继承了NSObject类的对象都是需要人工释放内存的,其他数据类型是系统自动回收的
- 在OC对象中,有一个引用计数器(占用4个字节),它表示的是自己被引用的次数
- 引用计数器:当使用alloc、new或者copy创建一个对象的时候,引用计数器会+1,若果给对象发送一个retain消息,引用计数器也会+1,如果给对象发送一个release消息的时候,引用计数器就会-1,一旦引用计数器变为0的时候,系统就会自动给对象发送一个dealloc消息(记住:不要自己调用该方法)释放相应的内存空间。另外调用retainCount对象方法能返回当前引用计数器的数值。有时我们会重写dealloc方法来处理一些善后工作
- 僵尸对象:已经被内存回收的对象,僵尸对象是不能再次被使用了
- 野指针:指向僵尸对象的指针。错误提示:EXC_BAD_ACCESS
- 空指针:没有指向任何地址的指针,可以理解成整数的0,当一个指针所致对象的引用计数器变为0 的时候,最好将该指针赋值nil(类似C语言中的NULL),以防野指针错误,因为在OC中让空指针发送消息是不会报错的
下面我们来简单通过代码了解一下内存管理
1 // 简单的内存管理 2 #import <Foundation/Foundation.h> 3 4 @interface Person : NSObject 5 6 @end 7 8 @implementation Person 9 // 重写dealloc方法 10 - (void)dealloc{ 11 NSLog(@"Persond对象被回收"); 12 [super dealloc]; 13 } 14 15 @end 16 17 int main() { 18 // 调用alloc方法,Person对象的引用计数器+1变为1 19 Person *p = [[Person alloc] init]; 20 21 [p release]; // 将引用计数器-1变为0,系统自动调用dealloc方法 22 return 0; 23 }
- retain和release的原则:谁alloc、new、copy,谁release;谁retain,谁release。也就是说每个alloc、new、copy对应一个release方法,每个retain方法对应一个release方法,不然的话,就会出现僵尸对象和野指针的错误
- 重写dealloc方法的注意项:首先它是对象方法,所以以减号‘-’开头,其次必须要调用父类的dealloc方法,并且放在方法的末尾
-
多个对象的内存管理
1 /*********Book对象*********/ 2 @interface Book : NSObject 3 { 4 int _price; // 价格 5 } 6 // price的setter和getter声明 7 - (void)setPrice:(int)price; 8 - (int)price; 9 10 @end 11 12 @implementation Book 13 14 // price的setter和getter实现 15 - (void)setPrice:(int)price{ 16 _price = price; 17 } 18 - (int)price{ 19 return _price; 20 } 21 22 // 重写dealloc方法 23 - (void)dealloc{ 24 NSLog(@"价格为%d的Book对象被回收", _price); 25 [super dealloc]; 26 } 27 28 @end 29 30 /*********Person对象********/ 31 @interface Person : NSObject 32 { 33 Book *_book; // Person对象的书属性 34 } 35 36 // _book的setter和getter声明 37 - (void)setBook:(Book *)book; 38 - (Book *)book; 39 40 @end 41 42 @implementation Person 43 44 // _book的setter和getter实现 45 - (void)setBook:(Book *)book{ 46 _book = book; 47 } 48 - (Book *)book{ 49 return _book; 50 } 51 52 // 重写dealloc方法 53 - (void)dealloc{ 54 NSLog(@"Person对象被回收"); 55 [super dealloc]; 56 } 57 @end 58 59 int main() { 60 61 Person *p = [[Person alloc] init]; 62 Book *b = [[Book alloc] init]; 63 64 p.book = b; 65 b.price = 15; 66 67 [b release]; 68 [p release]; 69 return 0; 70 }
注意:Xcode6.3版本默认情况下是让你使用ARC功能了(系统自动管理内存的功能),要想取消掉ARC功能请点击。这个程序运行完,似乎没什么内存管理问题,它都提示Book和Person对象被内存回收了。但是还有个问题:Person对象的里有个_book属性,当它指向Book对象的时候,代码中好像没有将Book对象的引用计数器+1,这就导致了在Person对象还没有被回收的时候,Book对象却已经被回收了,所以应该在_book指向Book对象的时候,向Book对象发送一个retain消息。也就是在执行完第64行代码后,Book对象的引用计数器+1变为2。第46行代码更改为:(修改_book的setter方法)
1 _book = [book retain]; // retain消息的返回值是对象本身
但是还不完善,因为这个retain没有对应的release。什么时候release呢?自然是当这个人“挂掉”了的时候。将Person对象的dealloc方法修改为:
1 - (void)dealloc{ 2 [_book release]; 3 NSLog(@"Person对象被回收"); 4 [super dealloc]; 5 }
如果突然有一天这个Person对象“换”了一本书的时候,又会出现什么情况呢?(只需改一下main函数)
1 int main() { 2 3 Person *p = [[Person alloc] init]; 4 Book *b1 = [[Book alloc] init]; 5 Book *b2 = [[Book alloc] init]; 6 b1.price = 15; 7 b2.price = 18; 8 p.book = b1; 9 p.book = b2; 10 11 [b1 release]; 12 [b2 release]; 13 [p release]; 14 return 0; 15 }
运行结果为啥只回收了一个Book对象呢?道理很简单:当你的Person对象里_book属性的值从b1更改为b2的时候没有对b1所指的对象发送release消息,这就好比重婚罪一样,你不能在没有解除婚姻的状况下和另一个对象结婚。所以我们还得对_book的setter方法进行最后完善
1 // _book的setter和getter实现 2 - (void)setBook:(Book *)book{ 3 if (book != _book) { // 为了防止错误的多次发送某一个对象的release消息 4 [_book release]; // 将就旧对象引用计数器-1,(如果是空指针,这行代码无效) 5 _book = [book retain]; // retain消息的返回值是对象本身 6 } 7 8 }
- 小结:
- 有几个alloc、new、copy就有几个release对应,有几个retain就有几个release与之对应。例如OC中的NSString类的对象是不用release的。什么时候retain呢?对象被指针所指,这时候就调用release。
-
非ARC机制下的setter方法的新规范:如果是基本数据类型,那还是跟以前一样;如果数据类型是对象的时候,那么它的新规范如下:
// 以Book对象为例 - (void)setBook:(Book *)book{ if (book != _book) { // 为了防止错误的多次发送某一个对象的release消息 [_book release]; // 将就旧对象引用计数器-1,(如果是空指针,这行代码无效) _book = [book retain]; // retain消息的返回值是对象本身 } }
- 重写dealloc方法:一定并且必须要在方法的末尾调用父类的dealloc方法[super dealloc],其次要把当前对象内的对象类的实例变量统统调用一次release方法
-
一旦对象类型的属性变多,那么相应的setter和getter代码就变得繁多而且没技术含量,此时Xcode就多了让系统一个自动添加含有retain和release的setter和getter,实际上就是在之前的@property基础上添加一点东西,格式如下:
// 括号里面可以添加一些修饰字 @property ( ) int age;
括号里面的修饰字总共可以分为4大类
-
与setter相关的内存管理参数:retain:OC对象、release旧值,retain新值assign(默认类型):直接赋值、基本数据类型copy:release旧值,copy新值
-
与是否生成setter有关readwrite(默认类型):同时生成setter和getter的声明和实现readonly:只生成getter的声明和实现
-
与多线程管理有关(鉴于有点复杂,有精力的人可以去研究一下)nonatomic:性能高(一般用这个)atomic():性能低(默认)
-
与getter和setter有关setter = setAbc:注意冒号别漏了,将setter自动生成的方法名改为setAbc:getter = abc,将getter自动生成的方法名更改为abc,例如将BOOL类型的rich数据的getter方法名改为isRich:getter = isRich
例如上一题代码中Person对象中的Book类型的实例变量我们科以这样写
1 @property (nonatomic, retain) Book *book;
这样一来_book的setter和getter声明和实现我们可以省去,能节省大量时间去思考别的问题,另外为方便代码的可读性,即使数据类型不是OC对象,assign这个默认类型的修饰字也不能省去,
还有个问题就是在main函数中,每alloc、new、copy一次出来的对象,我们都要写出一个对应的release方法,并且要放在代码的最末位置,一旦提前release了,有可能会使得这之后会访问这个僵尸对象,有什么方法让我们不需要操心是不是在合理的时间调用了release方法呢?autorelease能解决这个问题
- autorelease方法是对象方法,它的返回值是对象本身,只不过它把对象做了如下操作
- 将对象放在了一个自动释放池中
- 自动释放池被销毁时,会对池子里的所有对象发送一个release消息(不是将对象回收)
-
自动释放池湿热和创建出来的呢?
1 // 创建自动释放池 2 @autoreleasepool { 3 4 };
中括号开始代表了创建了自动释放池,中括号结束也表示着销毁了自动释放池,一般我们是把创建的对象语句以及对对象的相关操作语句放进自动释放池里,自动释放池可以嵌套自动释放池,这样就省去了[对象名 release]的书写,不过他也有一定的缺陷:可能没有及时的release对象,导致内存空间资源的浪费。代码实现:
1 /*********Book对象*********/ 2 @interface Book : NSObject 3 4 @property (nonatomic, assign)int price; 5 6 @end 7 8 @implementation Book 9 // 重写dealloc方法 10 - (void)dealloc{ 11 NSLog(@"价格为%d的Book对象被回收", _price); 12 [super dealloc]; 13 } 14 15 @end 16 17 /*********Person对象********/ 18 @interface Person : NSObject 19 20 @property (nonatomic, retain)Book *book; 21 22 @end 23 24 @implementation Person 25 26 // 重写dealloc方法 27 - (void)dealloc{ 28 [_book release]; 29 NSLog(@"Person对象被回收"); 30 [super dealloc]; 31 } 32 @end 33 34 int main() { 35 @autoreleasepool { 36 37 Person *p = [[[Person alloc] init] autorelease]; 38 Book *b1 = [[[Book alloc] init] autorelease]; 39 Book *b2 = [[[Book alloc] init] autorelease]; 40 41 42 b1.price = 15; 43 b2.price = 18; 44 p.book = b1; 45 p.book = b2; 46 47 } 48 return 0; 49 }