Object-C——内存管理
一、计数器
(一)、引用计数器的基本操作
1、 方法的基本使用
① retain :计数器+1,会返回对象本身
② release :计数器-1,没有返回值
③ retainCount :获取当前的计数器
④ dealloc
* 当一个对象要被回收的时候,就会调用
* 一定要调用[super dealloc],这句调用要放在最后面
1 #import "Person.h" 2 3 @implementation Person 4 5 6 // 当一个Person对象被回收的时候,就会自动调用这个方法 7 - (void)dealloc 8 { 9 NSLog(@"Person对象被回收"); 10 11 // super的dealloc一定要调用,而且放在最后面 12 [super dealloc]; 13 } 14 15 @end
2、 概念
① 僵尸对象 :所占用内存已经被回收的对象,僵尸对象不能再使用
② 野指针 :指向僵尸对象(不可用内存)的指针,给野指针发送消息会报错(EXC_BAD_ACCESS)
③ 空指针 :没有指向任何东西的指针(存储的东西是nil、NULL、0),给空指针发送消息不会报错
3、代码举例
1 #import <Foundation/Foundation.h> 2 #import "Person.h" 3 4 int main() 5 { 6 // 1 7 Person *p = [[Person alloc] init]; 8 9 //NSUInteger c = [p retainCount]; 10 11 //NSLog(@"计数器:%ld", c); 12 13 14 // 2 retain方法返回的是对象本身 15 [p retain]; 16 17 // 1 18 [p release]; 19 20 // 0 野指针:指向僵尸对象(不可用内存)的指针 21 [p release]; 22 23 [p retain]; 24 25 // -[Person setAge:]: message sent to deallocated instance 0x100109a10 26 // 给已经释放的对象发送了一条-setAge:消息: 27 p.age = 10; 28 //[p setAge:10]; 29 30 // 指针p变成空指针 31 //p = nil; 32 33 // EXC_BAD_ACCESS : 访问了一块坏的内存(已经被回收、已经不可用的内存 34 // 野指针错误 35 // OC不存在空指针错误,给空指针发送消息,不报错 36 [p release]; 37 [p release]; 38 [p release]; 39 [p release]; 40 41 [nil release]; 42 43 return 0; 44 }
(二) 多个对象之间的内存管理
1、 你想使用(占用)某个对象,就应该让对象的计数器+1(让对象做一次retain操作)
2、 你不想再使用(占用)某个对象,就应该让对象的计数器-1(让对象做一次release)
3、 谁retain,谁release
4、 谁alloc,谁release
1 // b-1 2 Book *b = [[Book alloc] init]; 3 // p-1 4 Person *p1 = [[Person alloc] init]; 5 6 //p1想占用b这本书 7 // b-2 8 [p1 setBook:b]; 9 10 // p-0 11 // b-1 12 [p1 release]; 13 p1 = nil; 14 15 // b-0 16 [b release]; 17 b = nil;
(三)、set方法的内存管理
1、内存管理代码规范:
① 只要调用了alloc,必须有release(autorelease)
② 对象不是通过alloc产生的,就不需要release
2、set方法
① 代码规范实现
1 //基本数据类型:直接复制 2 - (void)setAge:(int)age 3 { 4 _age = age; 5 } 6 7 // OC对象类型 8 - (void)setCar:(Car *)car 9 { 10 // 1.先判断是不是新传进来对象 11 if ( car != _car ) 12 { 13 // 2.对旧对象做一次release 14 [_car release]; 15 16 // 3.对新对象做一次retain 17 _car = [car retain]; 18 } 19 }
3、dealloc方法的代码规范实现
1 //一定要[super dealloc],而且放到最后面 2 // 对self(当前)所拥有的其他对象做一次release 3 - (void)dealloc 4 { 5 [_car release]; 6 [super dealloc]; 7 }
(四) @property参数
1、 控制set方法的内存管理
*retain : release旧值,retain新值(用于OC对象)
// retain : 生成的set方法里面,release旧值,retain新值 @property (retain) Book *book; @property (retain) NSString *name;
*assign : 直接赋值,不做任何内存管理(默认,用于非OC对象类型)
@property ( assign) int height;
*copy : release旧值,copy新值(一般用于NSString *)
2、控制生成或者不生成set方法
*readwrite :同时生成set方法和get方法(默认)
*readonly :只会生成get方法
@property (readwrite, assign) int height;
3、多线程管理
*atomic :性能低(默认)
*nonatomic :性能高
@property (nonatomic, assign, readwrite) int weight;
4、控制set方法和get方法的名称
*setter : 设置set方法的名称,一定有个冒号:
*getter : 设置get方法的名称
// 返回BOOL类型的方法名一般以is开头 @property (getter = isRich) BOOL rich;
(五) 循环引用
1、 @class
① 作用:仅仅告诉编译器,某个名称是一个类
② 开发中引用一个类的规范
*在.h文件中用@class来声明类
1 #import <Foundation/Foundation.h> 2 #import "Card.h" 3 // @class仅仅是告诉编译器,Card是一个类 4 //@class Card; 5 6 @interface Person : NSObject 7 8 @property (nonatomic, retain) Card *card; 9 10 @end
* 在.m文件中用#import来包含类的所有东西
1 #import "Person.h" 2 #import "Card.h" 3 4 @implementation Person 5 6 - (void)dealloc 7 { 8 NSLog(@"Person被销毁了"); 9 [_card release]; 10 11 [super dealloc]; 12 } 13 14 @end
2、循环retain
①问题
*A对象retain了B对象,B对象retain了A对象
*这样A、B对象永远无法释放
②两端循环引用解决方案
* 一端用retain
* 一端用assign
(六)、 autorelease
1、autorelease的基本用法
① 会将对象放到一个自动释放池中
② 当自动释放池被销毁时,会对池子里面的所有对象做一次release操作
③ 会返回对象本身
④ 调用完autorelease方法后,对象的计数器不变
2、autorelease的好处
① 不用再关心对象释放的时间
② 不用再关心什么时候调用release
3、autorelease的使用注意
① 占用内存较大的对象不要随便使用autorelease
② 占用内存较小的对象使用autorelease,没有太大影响
4、错误写法
① alloc之后调用了autorelease,又调用release
1 @autoreleasepool 2 { 3 // 1 4 Person *p = [[[Person alloc] init] autorelease]; 5 6 // 0 7 [p release]; 8 } 9
② 连续调用多次autorelease
1 @autoreleasepool 2 { 3 Person *p = [[[[Person alloc] init] autorelease] autorelease]; 4 }
5、自动释放池
① 在iOS程序运行过程中,会创建无数个池子。这些池子都是以栈结构存在(先进后出)
② 当一个对象调用autorelease方法时,会将这个对象放到栈顶的释放池
6、自动释放池的创建方式
@autoreleasepool
{
}
7、代码举例
1 void test() 2 { 3 @autoreleasepool 4 {// { 开始代表创建了释放池 5 6 // autorelease方法会返回对象本身 7 // 调用完autorelease方法后,对象的计数器不变 8 // autorelease会将对象放到一个自动释放池中 9 // 当自动释放池被销毁时,会对池子里面的所有对象做一次release操作 10 Person *p = [[[Person alloc] init] autorelease]; 11 12 p.age = 10; 13 14 15 16 @autoreleasepool 17 { 18 // 1 19 Person *p2 = [[[Person alloc] init] autorelease]; 20 p2.age = 10; 21 22 23 } 24 25 26 Person *p3 = [[[Person alloc] init] autorelease]; 27 28 29 } // } 结束代表销毁释放池 30 }