我们知道在程序运行过程中要创建大量的对象,和其他高级语言类似,在ObjC中对象时存储在堆中的,系统并不会自动释放堆中的内存(注意基本类型是由系统自己管理的,放在栈上)。如果一个对象创建并使用后没有得到及时释放那么就会占用大量内存。其他高级语言如Java都是通过垃圾回收机制来解决这个问题的,但在OjbC中并没有类似的垃圾回收机制,因此它的内存管理就需要由开发人员手动维护。
一、什么事引用计数
在Xcode4.2及之后的版本中由于引入了ARC(Automatic Reference Counting)机制,程序编译时Xcode可以自动给你的代码添加内存释放代码,如果编写手动释放代码Xcode会报错,因此现在我们要来实验手动内存管理(MRC),必须按照以下的步骤手动关闭ARC,这样才有助于你理解ObjC的内存回收机制。在ObjC中使用引用计数器来管理内存,在ObjC中每个对象内部都有一个与之对应的整数(retainCount),当一个对象在创建之后它的引用计数器为1,当调用这个对象的alloc、retain、new、copy方法之后引用计数器自动在原来的基础上加1(ObjC中调用一个对象的方法就是给这个对象发送一个消息),当调用这个对象的release方法之后它的引用计数器减1,如果一个对象的引用计数器为0,则系统会自动调用这个对象的dealloc方法来销毁这个对象。
二、能够让引用计数增加的操作
在ObjC中 alloc 、new 、retain 、 copy 会使对象的引用计数+1;addsubview、addobject、push、 property也会使对象的引用计数+1;对象打点调用 也会让引用计数+1;静态方法来创建的对象 引用计数系统+1 , 但是我们不用考虑释放,如果通过静态方法创建的 在某个时刻会被释放掉,但又想去使用这个对象 就可以用调用retain 方法
PeopleModel *p = [[PeopleModel alloc] init];
// 输出引用计数
NSLog(@"p的引用计数 为%ld",p.retainCount);
// retain保留
PeopleModel *p1 = [p retain];
// 引用计数是看的对象本身有几条绳
NSLog(@"内存地址 %p %p",p1,p);
NSLog(@"p1的引用计数 为%ld",p1.retainCount);
NSLog(@"p的引用计数 为%ld",p.retainCount);
UILabel *lab = [[UILabel alloc] initWithFrame:self.view.bounds];
NSLog(@"lab 引用计数 %ld",lab.retainCount);
[self.view addSubview:lab];
NSLog(@"lab ---- 引用计数 %ld",lab.retainCount);
// 通过静态方法创建对象也会让引用计数+1,不用我们去释放,由系统自动去释放
//
NSArray *array1 = [NSArray arrayWithObject:p];
[array1 objectAtIndex:0];
PeopleModel *pp = [[PeopleModel alloc] init];
NSLog(@"pp ---- %ld",pp.retainCount);
NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithCapacity:0];
[mutableArray addObject:pp];
NSLog(@"pp ---- %ld",pp.retainCount);
三、能够让引用计数减少的操作
有让引用计数增加的操作,对应的也会有让其减少的操作,release 、autoRelease 会让对象的引用计数 -1;remove*也能让引用计数 -1;
DogModel *dog1 = [[DogModel alloc] init];
// retainCount 用来获取引用计数的
NSLog(@"狗1 的引用计数为 %ld",dog1.retainCount);
// retain 保留
DogModel *dog2 = [dog1 retain];
// 为什么会两次都输出 引用计数为2 ???
// 要看的是对象的本身
NSLog(@"狗1 的引用计数为 %ld",dog1.retainCount);
NSLog(@"狗2 的引用计数为 %ld",dog2.retainCount);
// release 释放
[dog1 release];
NSLog(@"狗1 释放后引用计数为 %ld",dog1.retainCount);
// 当引用计数为0 的时候 会调用dealloc方法
[dog2 release];
NSArray *array1 = [[NSArray alloc] initWithObjects:@"1",@"2",@"3",nil];
[array1 objectAtIndex:0];
[array1 release];
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:0];
DogModel *dog_1 = [[DogModel alloc] init];
NSLog(@" dog—1 引用计数 %ld",dog_1.retainCount);
// +1
[array addObject:dog_1];
// 移除 -1
[array removeObject:dog_1];
NSLog(@"添加到数组之后 dog—1 引用计数 %ld",dog_1.retainCount);
[dog_1 release];
[array release];
UIView *view = [[[UIView alloc] init] autorelease];
//调用了autorelease方法后面就不需要手动调用release方法了
//由于autorelease是延迟释放,所以后面仍然可以使用view
但是要注意一下下面说的野指针的问题
PeopleModel *p = [[PeopleModel alloc] init];
// 输出引用计数
NSLog(@"p的引用计数 为%ld",p.retainCount);
// retain保留
PeopleModel *p1 = [p retain];
// 引用计数是看的对象本身有几条绳
NSLog(@"内存地址 %p %p",p1,p);
NSLog(@"p1的引用计数 为%ld",p1.retainCount);
NSLog(@"p的引用计数 为%ld",p.retainCount);
// release autoRelease 释放
// 会让对象的引用计数-1
[p1 release];
NSLog(@"p1的引用计数为 %ld",p1.retainCount);
// 当引用计数为0的时候,会调用本类的dealloc方法
// [p release];
//上面调用过release方法,p1指向的对象就会被销毁,但是此时变量p1中还存放着Person对象的地址,
//如果不设置p1=nil,则p就是一个野指针,它指向的内存已经不属于这个程序,因此是很危险的
p1=nil;
//如果不设置p1=nil,此时如果再调用对象release会报错,但是如果此时p已经是空指针了,
//则在ObjC中给空指针发送消息是不会报错的
而对于全局的变量我们要在dealloc中去释放掉,因为我们毕竟不知道在哪个位置会使用到,所以不能在创建之后就释放
// 本类对象被销毁时调用
- (void)dealloc{
[_array1 release];
// 全局变量在dealloc方法中释放
[_array3 release];
[_array2 release];
[super dealloc];
// 一定要调用super的dealloc方法,而且最好放在最后面调用
}
那现在我们就可以考虑一下我们以前的set方法是不是有问题了呢?答案肯定是的,如果一个属性中 赋值相同的对象,这时在set方法中就要作如下判断,为什么要加判断 当创建对象时 引用计数为1 self.之后对象的引用计数为2 ( 创建对象那次的+1 要release掉) 此时引用计数为1 如果没有判断直接会对象的引用计数为0 就会调用dealloc方法 就会造成数据丢失
- (void)setSmallDog:(DogModel *)smallDog{
if (_smallDog != smallDog) {
[_smallDog release];
[smallDog retain];
}
_smallDog = smallDog;
}
四、内存管理原则
从上面的用法中我们可以总结出来内存管理的基本原则,那就是 谁创建,谁释放;谁引用,谁管理,简单理解就是谁 retain 了 谁就 release
五、属性描述参数
我们开发的时候也不是很经常的去写set/get方法,一般都采用描述的方式来创建属性,描述的时候有很多的参数,我们来看一下参数的意义
@property (nonatomic,retain) NSMutableArray *array1;
@property (nonatomic,retain) DogModel *smallDog;
@property (nonatomic,assign) id test;
@property (nonatomic,assign) int ceshi;
@property (nonatomic,copy) NSString *name;
其实一般我们遵循下面的使用步骤就可以了
id assign,NSString copy,其他的都用retain