一、内存管理的方式
***整个iOS内存管理的核心是:引用计数。
1、C语言的内存管理,管理内存的开辟(malloc)和回收(free)
2、但是OC和C语言不一样,不直接管理内存的开辟和回收。管理的是引用计数。通过控制引用计数的加减,来实现内存管理,是一种间接管理内存的方式。
引用计数从0到1表示开辟了内存空间,1到0表示内存空间被系统回收了。
3、我们所谓的内存管理,其实就是管理的引用计数的加和减
(一)垃圾回收(gc)Garbage Collection
程序员只需要开辟内存空间,不需要用代码显示地释放,系统来判断哪些空间不再被使用,并回收这些内存空间,以便再次分配。整个回收的过程不需要写任何代码,由系统自动完成垃圾回收。Java开发中一直使用的就是垃圾回收技术。
(二)MRC(Manual Reference Count)
引用计数:OC采用引用计数机制管理内存,当一个新的引用指向对象时,引用计数器就递增,当去掉一个引用时,引用计数就递减。当引用计数到零时,该对象就将释放占有的资源。
人工引用计数:内存的开辟和释放都由程序代码进行控制。相对垃圾回收来说,对内存的控制更加灵活,可以在需要释放的时候及时释放,对程序员的要求较高,程序员要熟悉内存管理的机制。
1、+alloc:开辟内存空间,让被开辟的内存空间的引用计数变为1。这是由0到1的过程
此方法是继承自NSObject的方法,用于创建对象,给开辟的堆区域空间清0,同时将对象的引用计数设置为1,是对象引用计数从0到1得过程。
<span style="font-size:14px;">Person *p = [[Person alloc] init];
//NSObject这个类提供了一个查看对象引用计数的方法
//- (NSInteger)retainCount;
NSLog(@"p的引用计数为%lu,p:%p %p",[p retainCount],p,&p);
//alloc是重新开辟空间,所以只要是alloc创建出来的对象,引用计数此时肯定是1
Person *p1 = [[Person alloc] init];
NSLog(@"p1的引用计数为%lu,p1:%p",[p1 retainCount],p1);</span>
dealloc
<span style="font-size:14px;">- (void)dealloc{
NSLog(@"%@被释放了",self);
[super dealloc];
}</span>
2、-retain这个方法也是继承自NSObject,他的作用是将receiver(对象)的引用计数加1,并且返回receiver(对象)的首地址
<span style="font-size:14px;"> [p retain];
NSLog(@"p的引用计数为%lu,p:%p %p",[p retainCount],p,&p);
Person *p3 = [p retain];
NSLog(@"p3的引用计数为%lu,p3:%p %p",[p3 retainCount],p3,&p3);</span>
3、-copy这个方法也是继承自NSObject的,作用是拷贝一个 receiver 的副本,receiver所对应的对象引用引用计数保持不变,副本的引用计数设置为1,返回副本的首地址。
并不是所有的对象都能接收copy消息,只有接受了NSCopying协议,并且实现了copyWithZone:方法的对象才能接收copy消息,否则就会出现异常。
"main.h":
Student *stu = [[Student alloc]initWithName:@"zhangsan" age:18 gender:@"男"];
[stu retain];
NSLog(@"stu 引用计数是%lu, stu = %p",[stu retainCount],stu);
Student *stu1 = [stu copy];
//伪拷贝,相当于直接赋值,并没有生成新的拷贝对象
- (id)copyWithZone:(NSZone *)zone{
return [self retain];
}
//真拷贝之浅拷贝
- (id)copyWithZone:(NSZone *)zone{
Student *student = [[Student alloc]initWithName:self.name age:self.age gender:self.gender];
return student;
}
//真拷贝之深拷贝
//两个对象,两份内容
- (id)copyWithZone:(NSZone *)zone{
Student *student = [[Student alloc]initWithName:[self.name copy] age:self.age gender:[self.gender copy]];
return student;
}
4、-release此方法是继承NSObject的,作用是让receiver所对应的对象的引用计数减1,无返回值
一般来说,谁的引用计数增加,谁就需要执行release让其引用计数减少。
<span style="font-size:14px;">[p release];
//p = nil;
NSLog(@"p的引用计数为%lu,p:%p %p",[p retainCount],p,&p);
NSLog(@"p3的引用计数为%lu,p3:%p %p",[p3 retainCount],p3,&p3);
[p release];
[p release];
NSLog(@"p的引用计数为%lu,p:%p %p",[p retainCount],p,&p);</span>
(*)当一个对象的引用计数降到0的时候,注意此时retainCount打出来的值并不是0,而是1,不过此时对象已经被释放了。如果再去访问被释放的内存空间就会造成crash,也就是野指针。当对象的引用计数到0,内存回收之前,系统会自动执行该类的dealloc方法。
(*)野指针引发的崩溃有时有,有时没有。如果被释放的内存区域没有被使用,访问是不会出现问题的,但是一旦这块被回收的内存空间被使用了,再去访问就会出现异常。
5、-autorelease
//如何用NSMutableArray实现栈和队列的管理方式
**栈:添加元素我们采用addObject:,删除元素我们采用的是removeLastObject方法,达到先进后出得效果。
/*
while(arr[count] != 0){
[[arr lastObject] release];
[arr removeLastObject];
}
*/
**队列:只不过把方法改成 removeObjectAtIndex:0就可以了
Person *p1 = [[Person alloc]init];
Person *p2 = [[Person alloc]init];
[p1 retain];
[p1 retain];
[p2 retain];
NSLog(@"p1引用计数为 %lu,p2引用计数为%lu, %p, %@",[p1 retainCount],[p2 retainCount],p1,p2);
(**)[[NSAutoreleasePool alloc]init] 与 [pool release]可以看作是一个括号,在括号里面向对象发送autorelease消息,此时被pool管理
(**)pool先记下,都是谁执行了autorelease消息(就像可变数组一样,将接收autourelease消息的对象逐一加到pool里面)
(**)当pool不再需要的时候(销毁),会向pool中记录的receiver(倒序)发送一个release消息
//初始化一个池子
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
[p1 autorelease];
NSLog(@"p1引用计数为 %lu,p2引用计数为%lu, %p, %@",[p1 retainCount],[p2 retainCount],p1,p2);
[p1 autorelease];
NSLog(@"p1引用计数为 %lu,p2引用计数为%lu, %p, %@",[p1 retainCount],[p2 retainCount],p1,p2);
[p1 autorelease];
NSLog(@"p1引用计数为 %lu,p2引用计数为%lu, %p, %@",[p1 retainCount],[p2 retainCount],p1,p2);
[p2 autorelease];
NSLog(@"p1引用计数为 %lu,p2引用计数为%lu, %p, %@",[p1 retainCount],[p2 retainCount],p1,p2);
[p2 autorelease];
//释放池子
[pool release];
@autoreleasepool {
[p1 autorelease];
[p1 autorelease];
[p1 autorelease];
[p2 autorelease];
[p2 autorelease];
}
(三)ARC(Auto Reference Count)
自动引用计数:iOS 5.0的编译器特性,它允许用户只开辟空间,不用去释放空间。它不是垃圾回收!
它的本质还是MRC,只是编译器帮程序员默认加了释放的代码。
(四)iOS的内存管理
iOS支持两种内存管理方式:ARC和MRC。
MRC的内存管理机制是:引用计数。
ARC是基于MRC的。