废话少说,进入正题
内存管理其本身就是一个引用计数机制。
每次 创建 (alloc/new/copy/mutableCopy)或者持有对象(retain),使其引用计数 +1;
每次 释放(release)使其引用计数 -1;
当引用计数为 0 时,对象 销毁(dealloc);
内存管理的思考方式
- 自己生成的对象,自己持有
- 非自己生成的对象,自己也能持有
- 不再需要自己持有的对象时释放
- 非自己持有的对象无法释放
这四句话,引出了内存管理的本质:“谁持有,谁释放”
通过代码感受一下:
// 自己生成的对象,自己持有
id obj = [[NSObject alloc] init];
// 非自己生成的对象,自己也能持有
id obj1 = [NSMutableArray array];
// 不再需要自己持有的对象时释放
[obj release];
[obj1 release];
// 非自己持有的对象无法释放
// 注意 :obj 调用 release 之后已被释放,所以不再持有对象
[obj release]; // 引发崩溃,obj 不再持有对象,无法释放
ARC 和 MRC 的区别
MRC (manual reference counting 手动引用计数)
MRC 中,对象的创建、持有和释放都需要程序猿自己来管理,上代码:
- (void)test
{
id obj = [[NSObject alloc] init]; // 此时 obj 的引用计数为 1
}
当上面的方法执行完,obj 因为引用计数为 1,系统不会回收,造成资源的浪费。
通常我们的做法是:
- (void)test
{
id obj = [[NSObject alloc] init]; // 此时 obj 的引用计数为 1
[obj retain]; // obj 的引用计数 +1,此时 obj 的引用计数为 2
[obj release]; // obj 的引用计数 -1,此时 obj 的引用计数为 1
[obj release]; // obj 的引用计数 -1,此时 obj 的引用计数为 0
}
obj 执行了 release 方法后,引用计数 -1,obj 的引用计数此时为 0,系统便会回收这块资源。
当有很多对象需要 release 的时候,脾气再好的程序猿也会焦虑,不仅是要选一个好地方调用,更要细心,以免写漏了
这时便出现了 autorelease,上代码:
- (void)test
{
id obj = [[[NSObject alloc] init] autorelease]; // 此时 obj 的引用计数为 1
}
有了 autorelease 后,autorelease 会在方法的对象超出其作用域使其引用计数自动 -1。
- (void)test
{
id obj = [[[NSObject alloc] init] autorelease]; // 此时 obj 的引用计数为 1
[obj retain]; // obj 的引用计数 +1,此时 obj 的引用计数为 2
[obj release]; // obj 的引用计数 -1,此时 obj 的引用计数为 1
}
obj 再方法执行完后,引用计数为 1,autorelease 此时会自动 release,系统便会回收此对象的资源。
但是,本着能躺着绝对不坐着的思想,下面介绍ARC
ARC (automatic reference counting 自引用用计数)
通过名字我们便知,手动引用计数(retain,release,autorelease)以后怕是无缘再见了。
上代码:
// 生产并持有对象
id obj = [[NSObject alloc] init];
// 生产并持有对象
id obj1 = [[NSObject alloc] init];
// obj1 赋值给 obj,此时 obj 和 obj1 共同持有同一个对象,
// obj 之前持有的对象立即被自动释放
obj = obj1;
在 ARC 环境下,超出作用域后自动释放对象,所以 obj 和 obj1 也会被释放。
还需要知道的是,在 ARC 中使用 retain、release、autorelease,会引发编译器报错。
ARC 既然这么好,我们就不再需要关心内存管理了吗?
循环引用
直接看案发现场:
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, strong) id strongObj;
- (void)setObject:(id)strongObj;
@end
---------------------------------------------
#import "Person.h"
@implementation Person
- (void)setObject:(id)strongObj
{
_strongObj = strongObj;
}
@end
---------------------------------------------
#import <Foundation/Foundation.h>
@interface Dog : NSObject
@property (nonatomic, strong) id strongObj;
- (void)setObject:(id)strongObj;
@end
---------------------------------------------
#import "Dog.h"
@implementation Dog
- (void)setObject:(id)strongObj
{
_strongObj = strongObj;
}
@end
---------------------------------------------
{
Person *personObj = [[Person alloc] init];
Dog *dogObj = [[Dog alloc] init];
[personObj setObject:dogObj];
[dogObj setObject:personObj];
}
上面的代码在超出作用域后,obj 和 obj1 变量自动释放。但是 obj 的 strongObj 强引用着 obj1 的 strongObj,obj1 的 strongObj 强引用着 obj 的 strongObj,就像两只大手,互相牵着,即使代码已超出作用域,对象还是无法得到释放,此时就会发生内存泄漏。
内存泄漏是指应该销毁的对象在超出其生命周期后依旧存在。
Strong / Weak
上面的例子引出了 strong 这个修饰符:
@property (nonatomic, strong) id strongObj; // 强引用
在声明成员变量时,有很多修饰符,怎样合理使用修饰符便是接下来要讲的。
既然有 strong 这样的强引用修饰符,那肯定会有弱引用修饰符 weak:
@property (nonatomic, weak) id weakObj; // 弱引用
上面的例子中,两个对象的成员变量都为强引用,在对象该被销毁时,因为两个对象的成员变量都强引用着对方,造成内存泄漏。解决这一问题,只需把其中一个对象的成员变量修饰符改成 weak 即可。
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, strong) id strongObj; // 强引用
- (void)setObject:(id)strongObj;
@end
---------------------------------------------
#import "Person.h"
@implementation Person
- (void)setObject:(id)strongObj
{
_strongObj = strongObj;
}
@end
---------------------------------------------
#import <Foundation/Foundation.h>
@interface Dog : NSObject
@property (nonatomic, weak) id weakObj; // 弱引用
- (void)setObject:(id)strongObj;
@end
---------------------------------------------
#import "Dog.h"
@implementation Dog
- (void)setObject:(id)strongObj
{
_strongObj = strongObj;
}
@end
---------------------------------------------
{
Person *personObj = [[Person alloc] init];
Dog *dogObj = [[Dog alloc] init];
[personObj setObject:dogObj];
[dogObj setObject:personObj];
}
现在把 Dog 的成员变量修饰符改为 weak,当上面的代码执行完,对象都会被销毁。我们来分析一下上面的执行步骤:
- personObj 生成并持有对象;
- dogObj 生成并持有对象;
- personObj 的成员变量强引用 dogObj;
- dogObj 的成员变量弱引用 personObj;
因为 weak 并不会真正持有对象,在方法执行完后,personObj 立即被销毁。dogObj 的成员变量因为是被 weak 修饰的,没有强引用 personObj,所以 dogObj 也会被销毁。
常见的 weak 使用场景为 delegate、block、NSTimer 和 weak singleton