什么是内存管理?
内存管理,顾名思义就是对系统的内存进行管理。那么就会有人有疑问了,为什么我们以前从来都没做个内存管理,为什么程序依旧可以运行呢?这就与我下面介绍的MRC与ARC有关了。
ARC - 自动引用计数机制(Automatic Reference Counting)
在oc进行对象创建的时候会自动创建一个用来管理对象的引用计数器,并且更具创建的关键字,自动将引用计数置数。它采用的是“语法糖”机制,即当创建一个对象时,将对象像“糖”一样“包”起来,即创建对象后自动在其后面添加释放语句。
而在我们平时编写代码时,编译器会默认采用ARC模式,所以程序会自动帮我们回收空间,不需要我们做内存管理。
MRC - 手动引用计数机制(Manual Reference Counting)
虽然ARC可以自动帮我们回收空间,但苹果的编译机制决定了ARC的编译存在了一定的问题,经常使得编译的程序在实体机上运行时出现系统崩溃的情况,这是由于某些实体机没有ARC机制或者过早释放导致的。而且,在ARC模式下的空间由于是aoturelease的,会导致程序在运行过程中所占用的内存越来越大,不仅占用系统资源,甚至可能导致系统卡死等情况出现。
MRC即通过手动编写代码的方式来释放程序内存,使得我们不用某些对象时可以及时释放空间,节约内存。
在介绍内存管理之前,我们需要先了解oc的存储机制,在oc中内存大概分为以下几个区域:
1、堆区:主要存储自定义的对象等,它是所有应用共享的一块内存空间。当使用它的时候系统会自动分配内存,但需要手动释放空间。
2、栈区:主要存储变量、指针等,当使用时系统会自动创建自动回收,无需手动释放。
3、常量存储区:主要存储系统中的常量,当使用时会自动访问,不能修改。
4、自由存储区:存储其他数据。
5、特殊变量存储区域:存储全局变量与静态变量,自动释放。
当每一个OC对象创建时,都会创建一个相应的引用计数器,引用计数置1。
引用计数,即该对象被引用的次数,一般来说是指有几个指针访问着这块空间。当我们刚创建这个对象时,只有一个指针访问了这个对象所以引用计数置1。
在引用计数中,每一个对象负责维护对象所有引用的计数值。当一个新的引用指向对象时,引用计数器就递增,当去掉一个引用时,引用计数就递减。当引用计数到0时,该对象就将释放占有的资源。
在程序中我们可以通过retainCount来查看引用计数。
注:关键字:
alloc,new,copy为初始化一个对象,因此当使用这三个关键词创建对象时,对象的引用计数置1.
retain为使对象增加一个引用(指针),因此当使用retain时对象的引用计数+1.
release为释放对象的引用(指针),因此当对象release时对象引用计数-1.
那么如何进行内存管理呢?
如我们创建了一个对象
Person *person1 = [[Person alloc] init];
NSLog(@"Count = %ld", person1.retainCount);
这时Person的引用计数为1
Count = 1
当我们不需要这个对象时就需要释放这个对象,在oc中我们使用关键字release来释放对象的引用,这个对象引用为一因此只需要释放一次就可以了。
为了观察对象释放是否释放,我们需要在对象里重写dealloc方法,该方法是对象引用计数为0时自动走该方法释放对象空间。
Person:
@interface Person : NSObject<NSCopying,NSMutableCopying>
@property (nonatomic, retain)NSString *name;
@property (nonatomic, assign)NSInteger age;
@property (nonatomic, assign, getter=isLogin)BOOL login;
@property (nonatomic, copy)NSString *coString;
@end
dealloc:
- (void)dealloc{
NSLog(@"Person dealloc");
[super dealloc];
[_name release];
[_coString release];
}
在主函数中执行下列代码:
Person *person1 = [[Person alloc] init];
NSLog(@"Count = %ld", person1.retainCount);
[person1 release];
我们会发现运行后控制台显示如下:
Count = 1
Person dealloc
如果我们再次调用release时系统就会通知你该空间已被释放
overreleased while already deallocating; break on objc_overrelease_during_dealloc_error to debug
我们会发现当我们对person1进行操作输出时,系统仍然可以正常执行而且 person1.retainCount为1
然而,当我们多次运行后,就会程序崩溃的现象。
出现这样现象的原因是指针未能释放的问题
我们发现虽然对象的空间释放了,但指向对象的指针没有释放,所以当我们访问指针时他会随机访问系统区域中的某一块区域,这样的指针我们称之为“野指针”。所以当我们如下操作时:
Person *person1 = [[Person alloc] init];
NSLog(@"Count = %ld", person1.retainCount);
[person1 release];
person1.name = @"Flack";
NSLog(@"%@", person1.name);
person1指针会访问内存的某一块区域,当存在访问的值时系统就可以正常运行,但当它访问不到该属性使系统就会崩溃,并显示如下信息:
EXC_BAD_ACCESS(code=1, address=0x18)
总的来说,该内存已经释放而指针仍然存在造成了这个错误。在程序中我们可以通过关键字retain来增加对象的引用计数,如:
Person *person1 = [[Person alloc] init];
NSLog(@"Count = %ld", person1.retainCount);
[person1 retain];
NSLog(@"Count = %ld", person1.retainCount);
[person1 release];
运行结果如下:
Count = 1
Count = 2
我们发现程序没有被释放,说明程序的引用数增加了,过程如下:
那么我们再释放一次:
Person *person1 = [[Person alloc] init];
NSLog(@"Count = %ld", person1.retainCount);
[person1 retain];
NSLog(@"Count = %ld", person1.retainCount);
[person1 release];//释放一次
NSLog(@"Count = %ld", person1.retainCount);
[person1 release];//再次释放
运行结果如下:
Count = 1
Count = 2
Count = 1
Person dealloc
结果说明对象已经被释放了,那么我们可以得出结论:
当对象创建后,会自动创建一个引用计数器,并将引用计数置1.(关键字:alloc,new,copy)
当对象retain时,对象的引用计数+1(关键字:retain)
当对象release后,对象的引用计数-1(关键字:release)
当对象引用计数为0时,自动走dealloc方法释放空间。
由于指针仍然存在,导致retainCount不为0。
多对象的内存管理
知道了单个对象的内存管理,那么复合的多个对象内存应该如何管理呢?
如:某人拥有一本书,这本书属于某个人
在这个例子里我们可以得出两个类,一个是“人”,另一个是“书”。这两个类互相包含,如下:
Person.h:
#import "Book.h"
@interface Person : NSObject
@property (nonatomic, copy)NSString *name;
@property (nonatomic, retain)Book *book;
@end
Book.h:
#import "Person.h"
@interface Book : NSObject
@property (nonatomic, copy)NSString *name;
@property (nonatomic, retain)Person *person;
@end
我们需要重写dealloc方法来释放对象中的对象
Person.m:
- (void)dealloc{
NSLog(@"Person dealloc");
[super dealloc];
[_name release];
[_book release];
}
Book.m:
- (void)dealloc{
NSLog(@"Book dealloc");
[super dealloc];
[_name release];
[_person release];
}
但当我们在程序中执行如下代码时:
Person *person = [[Person alloc] init];
Book *book = [[Book alloc] init];
[person release];
[book release];
发现程序报错崩溃了
出现这样错误的原因是我们在程序中互相引用了两个对象,这种现象我们称为-循环引用
只需要将Book和Person中的某一个变成声明即可以消除循环引用现象
//#import "Person.h"
@class Person;
@interface Book : NSObject
@property (nonatomic, copy)NSString *name;
@property (nonatomic, retain)Person *person;
@end
当我们再次运行程序就会发现程序正常运行了。
为了保证程序的正常运行,我们通常将该对象中的retain改为弱引用assign
//#import "Person.h"
@class Person;
@interface Book : NSObject
@property (nonatomic, copy)NSString *name;
@property (nonatomic, assign)Person *person;
@end