1.栈区(stack):由编译器自动分配释放,函数的参数值,局部变量等值。
2.堆区(heap):一般由开发人员分配释放,若不释放,则可能会引起内存泄漏。
NSString* string = @"sdfsdf";//常量string->栈
NSInteger index = 0; //index->栈
NSMutableString* mString = [[NSMutableString alloc] initWithString:@"sdfsdf"];//mString->堆
Objective-C提供了两种种内存管理方式:manual reference counting(MRC,手动引用计数器),automatic reference counting(ARC,自动引用计数)。ARC作为苹果新提供的技术,苹果推荐开发者使用ARC技术来管理内存;
新建RetainCountObject类,重写其创建和销毁的方法
@implementation RetainCountObject
- (instancetype)init {
self = [super init];
if (self) {
NSLog(@"初始引用计数为 %ld",self.retainCount);
};
return self;
}
- (void)dealloc {
NSLog(@"对象被释放");
NSLog(@"release后的引用计数为 %ld", self.retainCount);
[super dealloc];
}
@end
在ViewDidLoad方法中创建RetainCountObject对象,給object发送消息
RetainCountObject * object = [[RetainCountObject alloc]init];
[object retain];
NSLog(@"object引用计数为 %ld", object.retainCount);
[object release];
NSLog(@"objec引用计数为 %ld", object.retainCount);
[object release]; //将指针置nil,否则变为野指针
object = nil;
MRC_Project[40145:1469636] 初始引用计数为 1
MRC_Project[40145:1469636] object引用计数为 2
MRC_Project[40145:1469636] objec引用计数为 1
MRC_Project[40145:1469636] 对象被释放
注意一些特殊的情况:
NSString引用计数问题
如果我们尝试查看一个string的引用计数
NSString * str = @" hello"; NSLog(@"hello guys :%ld", str.retainCount);
MRC_Project[40189:1472607] hello :-1
NSString实际上是一个字符串常量,由栈管理,是没有引用计数的。
赋值不会拥有某个对象
NSString* title = object.title;
这里仅仅是指针赋值操作,并不会增加name的引用计数,需要持有对象必须要发送retain消息。
Dealloc
由于释放对象是会调用dealloc方法,因此重写dealloc方法来查看对象释放的情况,如果没有调用则会造成内存泄露。在上面的例子中我们通过重写dealloc让小狗被释放的时候打印日志来告诉我们已经完成释放。
在上面例子中,如果我们增加这样一个操作
//最后release object时
[object release];
NSLog(@"release后的引用计数为 %ld", self.retainCount);
[super dealloc];
MRC_Project[40314:1477373] release后的引用计数为 1
会发现获取到的引用计数为1,为什么不是0呢?
这是因为对引用计数为1的对象release时,系统知道该对象将被回收,就不会再对该对象的引用计数进行减1操作,这样可以增加对象回收的效率。
自动释放池,系统有一个现成的自动内存管理池,他会随着每一个mainRunloop的结束而释放其中的对像;自动释放池也可以手动创建,他可以让pool中的对象在执行完代码后马上被释放,可以起到优化内存,防止内存溢出的效果(如视频针图片的切换时、创建大量临时对象时等)。
autorelease:自动释放,使对象在超出指定的生存范围时能够自动并正确地释放 (release 即是立即释放)。
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc]init];
// do something
id obj = [[NSMutableArray alloc] init];
[obj autorelease];
[pool release];
// [pool drain];//GC(垃圾回收机制)环境没影响
对于所有调用过autorelease实例方法的对象,在pool release时,将调用release释放对象
@autoreleasepool {
// do something
}
MRC方法放回对象时需要autorelease
- (NSArray *)getArray{
NSArray* array = [NSArray array];
return array;
/*
NSArray* array = [[[NSArray alloc] init] autorelease];
return array;
*/
}
- (void)autoRelease_Test {
@autoreleasepool {
TestModel *model = [[TestModel alloc] init];
[model autorelease];
//model can dongSomething you want
NSLog(@"自动释放:end");
}
}
MRC_Project[2678:287011] 自动释放:end
MRC_Project[2678:287011] TestModel dealloc
可以看到,当自动释放调用后,model对象才被释放,因此在池子释放之前,model可以正常调用。
strong, weak, autoreleasing, unsafe_unretained
__strong ://强引用,持有所指向对象的所有权,无修饰符情况下的默认值。如需强制释放,可置nil。
比如我们常用的定时器:
NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:nil userInfo:nil repeats:NO];
相当于:
NSTimer* __strong timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:nil userInfo:nil repeats:NO];
当不需要使用时,强制销毁定时器:
[timer invalidate];
timer = nil;
__weak : //弱引用,不持有所指向对象的所有权,引用指向的对象内存被回收之后,引用本身会置nil,避免野指针。
比如避免循环引用的弱引用声明:
__weak typeof(self) weakSelf = self;
__autoreleasing //自动释放对象的引用,一般用于传递参数
比如一个读取数据的方法:
- (void)doSomething:(NSError **)error
当你调用时会发现这样的提示:
[self doSomething: (NSError *__autoreleasing *)]
这是编译器自动帮我们插入以下代码:
NSError* __autoreleasing error = nil;
[self doSomething:&error];
__unsafe_unretained //弱引用,内存被释放后有僵尸对象,会产生野指针,不建议使用
@property (nonatomic, assign) int value; //简单的赋值
@property (nonatomic, retain) NSArray* array; //引用计数器加一,指针复制
@property (nonatomic, copy) NSString* string; //生成新的内存区域,内容复制
@property (nonatomic, strong) NSNumber* number;//强引用,当所有指向同一块内存的强指针都赋空时,内存将被释放
@property (nonatomic, weak) NSData* data; //弱引用, 当所有指向同一块内存的强指针都赋空时,弱引用失效,为nil
@property (nonatomic, unsafe_unretained) NSData* data1; //不安全性弱引用,当所有指向同一块内存的强指针都赋空时,该对象将成为野指针,再次调用会导致程序崩溃
- (NSString *)string{
return _string;
}
- (void)setString:(NSString*)string{
_string = string;
//weak,strong,assign 的set方法
}
- (void)setString:(NSString*)string{
if(_string != string) {
[_string release];
_string = [string retain];
}
//retain 的set方法
}
- (void)setString:(NSString*)string{
if(_string != string) {
[_string release];
_string = [string copy];
}
//copy 的set方法
}
//释放属性对象
- (void)dealloc {
self.string = nil;
[super dealloc];
}
block本身是像对象一样可以retain,和release。但是,block在创建的时候,它的内存是分配在栈(stack)上,而不是在堆(heap)上。
@property(copy, nonatomic) void(^block)(void);
mrc中 copy会把block从栈上移动到堆上。
@property(strong, nonatomic) void(^block)(void);
arc即时由强引用strong将其从栈复制到堆
block 的内存管理:
当程序运行到这里时,stack 空间中有 shared 变量和 captured 变量。
这里可以看出,__block 变量开始是处于stack上的。
当程序运行到这里时,stack 空间中有 shared 变量,captured 变量和block1。
这里可以看出,block 类型的变量开始时也是处在stack上的。
当程序运行到这里时,stack 空间中有 shared 变量,captured 变量和block1。
这里值得注意的就是当我们直接修改stack 上的captured变量时,block1中的captured变量仍然是原来的数值10。事实上,从const 我们就可以看出,block1中的captured变量是不能被修改的而且是从stack原有变量的一个const 拷贝。在block1中访问的captured变量是const拷贝的,也就是说block1中captured = 10,而不是原有的stack上的值 20。当然,在block1中,我们也不能修改captured变量。
Copy block:
block在一开始是处在stack上的,这是为了考虑到效率的原因,但是,有时候是需要block的生命周期长于一开始的stack,这时,我们就通过copy block 来将block复制到heap。
当程序执行完 block2 = [block1 copy];时,__block 类型变量shared,被复制到了heap中,很显然,shared变量需要被block和block2共享(当然还有stack也要共享),而block2被移动到heap中,很可能生命周期会长于stack,所以,shared也被复制到了heap中。而block2中的captured 也被复制到了heap中。
当程序执行完 block3 = [block2 copy];时, 我们看到的是,block2 和block3 其实指向的是同一片内存空间。事实上,block的数据结构中,保存了引用计数,而对于copy到heap中的block 再copy时,行为同普通对象retain一样,会使引用计数+1。那么如果我们对[block retain]会如何呢? 实际上什么都没有发生,至少在现在的runtime版本下。因为retain中,不仅有引用计数+1在,而且retain的返回值,必须同返回调用对象的地址一样,而block的地址是可能变化的(stack or heap),所以,这里retain的行为几乎是被忽略掉的。
当heap中的block变量先于stack被销毁时,如调用 [block2 release]; [block3 release];,heap中的block2,block3 由于引用计数为0 而被销毁,而 __block 变量shared则还在heap中,因为stack还要使用,block1 也要使用。
当heap中的block变量晚于stack时,显然,stack 被清除,function中也啥都没了。
最后,当block2 和block3 都被release之后。则恢复到最初状态
项目 -> Build Phases -> Compile Sources 找到要修改的文件
如果是ARC工程添加MRC文件则输入:-fno-objc-arc
如果是MRC工程添加ARC文件则输入:-fobjc-arc
引用方式:
copy:拷贝,复制一个对象并创建strong关联,引用计数为1 ,原来对象计数不变。
assign:赋值,不涉及引用计数的变化,弱引用。ARC中对象不使用assign,但原始类型(BOOL、int、float)仍然可以使用。
retain:持有,对原对象引用计数加1,强引用。ARC中使用strong。
weak:赋值(ARC),比assign多了一个功能,对象释放后把指针置为nil,避免了野指针。
strong:持有(ARC),等同于retain。
在你打开ARC时,你是不能使用retain、release、autorelease 操作的,原先需要手动添加的用来处理内存管理的引用计数的代码可以自动地由编译器完成了,但是你需要在对象属性上使用weak 和strong, 其中strong就相当于retain属性,而weak相当于assign,基础类型只需声明非原子锁即可。
以下代码在ARC之前是可能会行不通的,因为在手动内存管理中,从NSArray中移除一个对象时,这个对象会发送一条release消息,可能会被立即释放。随后NSLog()打印该对象就会导致应用崩溃
id obj = [array objectAtIndex:0];
[array removeObjectAtIndex:0];
NSLog(@"%@", obj);
在ARC中这段代码是完全合法的,因为obj变量是一个strong指针,它成为了对象的拥有者,从NSArray中移除该对象也不会导致对象被释
僵尸对象:内存已经被回收的对象。
野指针:指向僵尸对象的指针,向野指针发送消息会导致崩溃。
EXC_BAD_ACCESS
arc中默认的对象声明都是strong性质的,在两个或两个以上的类相互引用时,会导致循环引用,其中一方需要用weak修饰,才不会造成retainCycle,如:delegate 属性用weak声明;mrc中即用assign修饰 。
在block中引用block所属的类、实例变量或类的属性也会导致循环引用
self.block = ^{
[self doSomething];
};
block是会对内部的对象进行一次retain。也就是说,self会被retain一次。当self释放的时候,需要block释放后才会对self进行释放,但是block的释放又需要等self的dealloc中才会释放。如此一来变形成了循环引用,导致内存泄露。
arc中用__weak修饰self、mrc中用__block修饰,如下代码:
__weak ViewController* weakSelf = self;//arc
//__block ViewController* weakSelf = self;//mrc
self.block = ^{
[weakSelf doSomething];
};
这个问题常见于循环次数较大,循环体生成的对象占用内存较大的情景。
例子代码:
for (int i = 0; i < 10000; i ++) {
Person * soldier = [[Person alloc]init];
[soldier fight];
}
该循环内产生大量的临时对象,直至循环结束才释放,可能导致内存泄漏,解决方法和上文中提到的自动释放池常见问题类似:在循环中创建自己的autoReleasePool,及时释放占用内存大的临时变量,减少内存占用峰值。
for (int i = 0; i < 10000; i ++) {
@autoreleasepool {
Person* soldier = [[Person alloc]init];
[soldier fight];
}
}
然而有时候autoReleasePool也不是万能的:
for (int i = 0; i < 2000; i ++) {
CGSize size = [UIImage imageNamed:[NSString stringWithFormat:@"%d.jpg",i]].size;
}
用imageNamed方法加载图片占用Cache的内存,autoReleasePool也不能释放。
for (int i = 0; i < 2000; i ++) {
@autoreleasepool {
CGSize size = [UIImage imageWithContentsOfFile:filePath].size;
}
}
例如递归死循环,for、while无限循环,NSTimer无限调用
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
self.view = nil;
self.data = nil;
}
ARC & MRC demo:
https://github.com/zhengmiaokai/ARC_Project.git
https://github.com/zhengmiaokai/MRC_Project.git