OC的内存管理
为什么需要内存管理?
我们都知道 OC 中对象所申请的空间是在堆段。那么堆段有个要求,当程序员在不需要使用的时候,需要手动将这片空间释放。
那么,什么时候释放呢?
内存管理常见的三个问题
1、【内存泄漏】
堆空间没有释放。
2、【提前释放】
使用已经释放的空间(某个地址未使用完毕已经被释放),称为:
p[5] = 'A';
free(p);
printf("%c\n",p[5]);
3、【重复释放】
重复释放同一个空间
【后果很严重】
a.out(478,0x7fff7477d310) malloc: *** error for object 0x7f8ec2c03940: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
Abort trap: 6
引用计数
【引用计数】也叫【保留计数】, 在OC里面,引用就是指针。
引用计数是解决内存管理一种非常有效的方式。
采用类似讨论组的计数管理方式,当多一个指针使用堆,计数加1,少一个指针使用堆,计数减1。当计数减到0,释放堆。
OC 中引用计数的量保存在 NSObject 类中,同时由于 OC 中的对象都要求继承于 NSObject 类,所以所有的对象都有了引用计数的值啦。
手动内存管理(MRC)
XCode 中打开 MRC
工程 —> Target —> BuildSetting —> 搜索【gar】
将 【YES】—>【NO】
内存管理的方法
retain作用: 保留对象,对象的引用计数+1
alloc作用: 创建对象,将新对象的引用计数初始化为1
copy作用: 拷贝对象,如果生成新对象,将新对象的引用计数设置为1,
如果没有生成新对象,引用计数+1(后期会详细介绍)
release作用: 将对象的引用计数-1
黄金法则
(1) 凡是用alloc,retain,new(或使用new开头的方法),copy(或使用copy开头的方法),mutableCopy(或使用mutableCopy开头的方法)【创建】的对象,都必须使用release或autorelease方法【释放】
(2) 谁创建谁释放(哪个类创建,哪个类释放)
常见的、隐蔽的内存问题
构造方法中创建的对象
对象(对象的指针)的成员变量在构造方法中创建(实例化),应在析构方法中释放。
- (instancetype) init {
self = [super init];
if (self) {
_arr = [[NSMutableArray alloc] init];
}
return self;
}
- (void) dealloc {
[_arr release];
}
setter 方法
(1) 判断是否为同一指针
(2) 旧对象 release
(3) 新对象 retain
- (void) setArr: (NSMutableArray *) arr {
// 判读新对象是否是现在的同一对象
if (_arr != arr) {
// 释放之前保存的对象
// 如果之前的对象为 nil 也没有关系,因为可以向 nil 发送任何消息
[_arr release];
// 指向新对象,并且使对象引用计数增1
_arr = [arr retain];
}
}
- (void) dealloc {
// 在析构函数中释放对象
[_arr release];
}
【注意】系统函数中依然需要释放此对象
指针的重新赋值
(1) 就对象 release
(2) 新对象 retain
Dog * dog1 = [[Dog alloc] init];
[dog1 release];
// 在dog1指针 指向新对象时,需要在此之前,先释放之前的对象才行
dog1 = [[Dog alloc] init];
自动释放池
请写一个用于创建对象(Dog)的类方法, 如下所示:
+ (Dog *) dogWithName: (NSString *) name {
Dog * dog = [[Dog alloc] init];
dog.name = name;
// 这个创建的对象应该归谁释放呢?
return dog;
}
返回创建的对象应该归谁释放呢?
不可能归 dogWithName 释放,因为释放之后,调用此方法的函数就不能使用这个创建的对象了
如果归调用者创建,这好像又不满足“黄金法则”中规定的谁开辟,谁释放的原则。
【解决方式】采用自动释放池的方式:
自动释放池类似一个数组,进行延迟释放,不会马上计数器减1,而是将当前对象,放入最近的自动释放池。当池释放时,将每个池中的元素释放一次。
+ (Dog *) dogWithName: (NSString *) name {
Dog * dog = [[Dog alloc] init];
dog.name = name;
// 加入到最近的自动释放池
[dog autorelease];
return dog;
}
main.m
@autoreleasepool {
Dog * dog = [Dog dogWithName:@"xiaohuang"];
}
自动内存管理(ARC)
从上面 MRC 提到的几个问题看来,手动内存管理并不是件轻松的事。所以,Apple 公司提出了 自动内存管理(ARC)。
就是编译器自动完成堆空间的引用计数加减,自动释放。程序员不再写 retain release等方法。大大减轻了程序员的工作负担!
属性修饰词
ARC: strong, copy, weak
MRC: retain, copy, assign
retain, strong 作用: 保留对象,对象的引用计数+1
assign, weak 作用: 指向新对象, weak 会在对象消失是,自动变成 nil
alloc作用: 创建对象,将新对象的引用计数初始化为1
copy作用: 拷贝对象,如果生成新对象,将新对象的引用计数设置为1,
如果没有生成新对象,引用计数+1(后期会详细介绍)
release作用: 将对象的引用计数-1
原则上,ARC 不能写retain, copy 了, 只能写strong,如果不想retain,写weak
【注】但实际上ARC对这方面很宽松,写了retain, copy也没关系。
修饰指针关键字
__strong(强引用) 缺省属性,其修饰的对象指针,指向哪个对象,会对该对象自动retain,离开哪个对象,会对该对象自动release。
__weak(弱引用)其修饰的对象指针,指向任何对象都不会retain。这样的指针指向的对象随时可能消失。如果对象消失了,这个指针会自动变成nil。
iOS编程中,代理对象使用弱引用。
__unsafe_unretained 其修饰的对象指针,指向任何对象都不retain。当指向的对象消失,该指针不会变成nil,仍然指向已经释放的对象的空间。即变成野指针。
__autoreleasing 只用来修饰需要被传入地址的指针。如:
__autoreleasing NSError * error; &error;
总结
使用 ARC 非常方便,可还要注意 ARC 的局限性。
(1) ARC的局限
1.使用ARC,可能因为代码的不规范,导致内存提前释放。
//尤其使用AVAudioplayer类的时候,很可能造成提前释放。
- 导入一些第三方库,或者导入旧代码,这些代码不支持ARC。
(2) 解决ARC的局限
1.将不使用ARC的代码转成ARC代码
Edit —> Refactor —> Convert to ARC
(3) ARC非ARC混编
//同一个工程中,部分文件使用ARC,部分文件不使用ARC。
Build phase —–> Complie Source——>选中不想ARC的那个文件————>右侧双击并填:-fno-objc-arc