内存管理剖析(三)——iOS程序的内存布局markdown
iOS中是经过 【引用计数】 来管理OC对象的内存的。oop
retain
会让OC对象的引用计数+1,调用release
会让OC对象的引用计数-1。alloc
、new
、copy
、mutableCopy
方法返回了一个对象,再不须要这个对象时,要调用release
或者autorelease
来释放它。能够经过一下私有函数来查看自动释放池的状况布局
extern void _objc_autoreleasePoolPrint(void);
post
下面咱们经过案例来分析一波ui
//********* main.m ************
#import <Foundation/Foundation.h>
#import "CLPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
CLPerson *person = [[CLPerson alloc] init];
NSLog(@"retainCount ----- %zd",[person retainCount]);
}
return 0;
}
//********* CLPerson.h ************
#import <Foundation/Foundation.h>
@interface CLPerson : NSObject
@end
//********* CLPerson.m ************
#import "CLPerson.h"
@implementation CLPerson
-(void)dealloc {
[super dealloc];
NSLog(@"%s",__func__);
}
@end
复制代码
咱们从在main.m
里面经过alloc
建立一个CLPerson
实例,经过打印能够看到其引用计数为1
,atom
2019-08-27 09:12:45.362995+0800 MRCManager[10928:615055] retainCount ----- 1
复制代码
而且没有看到person
调用dealloc
方法,说明在main
函数结束以后,person
并无被释放。那么咱们在使用完person
以后给加上一句release
,以下spa
CLPerson *person = [[CLPerson alloc] init];
NSLog(@"retainCount ----- %zd",[person retainCount]);
[person release];
复制代码
此次的打印结果为
2019-08-27 09:12:45.362995+0800 MRCManager[10928:615055] retainCount ----- 1
2019-08-27 09:12:45.363226+0800 MRCManager[10928:615055] -[CLPerson dealloc]
复制代码
能够看到,person
走了dealloc
方法,也就是成功被释放了,缘由就是经过release
方法,使得自身的引用计数-1,1 - 1 = 0,而后系统便依据该引用计数的值将person
释放。OC的内存管理其实原理很简单。
咱们知道,Mac命令行项目,main
函数时从上至下,线性执行,走到return 0
,整个程序就退出结束了,所以像咱们案例中的情景,很容易判断该什么时候对对象进行release
操做。
在咱们经常使用的iOS项目里面,因为加入了RunLoop,程序会在main
函数里面一直循环,知道崩溃,或者手动关闭app。所以当一个对象被建立了以后,它什么时间会被使用,是很难肯定的。若是不调用release
,那么能够保证任什么时候间使用对象都是安全的,可是带来的问题即是,当对象再也不使用以后,它便会一直存留在内存里面,不被释放,这就是咱们常说的**【内存泄漏】**。
为此,苹果为咱们提供了autorelease
,在每次建立对象的时候调用
CLPerson *person = [[[CLPerson alloc] init] autorelease];
复制代码
这样,无需咱们手动调用[person release];
,系统会在某个合适的时间,自动对person
进行release
操做,这个“合适的时间”暂且理解成@autoreleasepool {}
大括号结束的时候。
#import <Foundation/Foundation.h>
#import "CLPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
CLPerson *person = [[[CLPerson alloc] init] autorelease];
CLPerson *person2 = [[[CLPerson alloc] init] autorelease];
NSLog(@" @autoreleasepool即将结束");
}
NSLog(@" @autoreleasepool已经结束");
return 0;
}
//********************** 打印信息 *******************
2019-08-27 09:40:29.388495+0800 MRCManager[10970:625654] @autoreleasepool即将结束
2019-08-27 09:40:29.388727+0800 MRCManager[10970:625654] -[CLPerson dealloc]
2019-08-27 09:40:29.388736+0800 MRCManager[10970:625654] -[CLPerson dealloc]
2019-08-27 09:40:29.388756+0800 MRCManager[10970:625654] @autoreleasepool已经结束
Program ended with exit code: 0
复制代码
上述案例仅仅针对一个对象这种简单状况来讨论。在iOS实际项目中,每每对象与对象之间是有不少关联的。咱们继续给上面的代码添加一个CLCat
对象
#import <Foundation/Foundation.h>
@interface CLCat : NSObject
-(void)run;
@end
***************************************************
#import "CLCat.h"
@implementation CLCat
-(void)run {
NSLog(@"%s",__func__);
}
-(void)dealloc {
[super dealloc];
NSLog(@"%s",__func__);
}
@end
复制代码
若是CLPerson
想拥有CLCat
,则须要对其做以下调整
#import <Foundation/Foundation.h>
#import "CLCat.h"
@interface CLPerson : NSObject
{
CLCat *_cat;
}
//拥有猫
-(void)setCat:(CLCat *)cat;
//获取猫
-(CLCat *)cat;
@end
***************************************************
#import "CLPerson.h"
@implementation CLPerson
-(void)setCat:(CLCat *)cat {
_cat = cat;
}
-(CLCat *)cat {
return _cat;
}
-(void)dealloc {
[super dealloc];
NSLog(@"%s",__func__);
}
@end
复制代码
这样就能够经过setCat
方法,把CLCat
对象设置到CLPerson
的_cat
成员变量上(拥有);经过cat
方法拿到成员变量_cat
(获取)。也就是说CLPerson
对象能够经过_cat
指针操纵一个CLCat
对象了。
int main(int argc, const char * argv[]) {
@autoreleasepool {
CLCat *cat = [[CLCat alloc] init];
CLPerson *person = [[CLPerson alloc] init];
[person setCat:cat];
[person.cat run];
[cat release];
[person release];
}
return 0;
}
***************** 打印结果 ****************
2019-08-27 10:22:11.086033+0800 MRCManager[11054:643966] -[CLCat run]
2019-08-27 10:22:11.086283+0800 MRCManager[11054:643966] -[CLCat dealloc]
2019-08-27 10:22:11.086294+0800 MRCManager[11054:643966] -[CLPerson dealloc]
Program ended with exit code: 0
复制代码
从打印结果看上去,行得通。可是注意[person.cat run];
是在 [cat release];
以前。若是是下面这样
int main(int argc, const char * argv[]) {
@autoreleasepool {
CLCat *cat = [[CLCat alloc] init];
CLPerson *person = [[CLPerson alloc] init];
[person setCat:cat];
[cat release];
[person.cat run];
[person release];
}
return 0;
}
复制代码
结果会是这样报错的缘由图中已经代表,因此,只须要确保
[cat release];
在 [person.cat run];
以后被调用便可。可是实际卡发中, [person.cat run];
什么时候被调用,是不肯定的,而且次数也不肯定,也就是说,咱们没法肯定[person.cat run];
会于什么时候在何处被最后一次调用,所以就没法肯定[cat release];
到底该写在哪里。为了保证不出现EXC_BAD_ACCESS
报错,能够干脆不写[cat release];
,但这就带来了内存泄漏问题。 问题的本质就是CLPerson
并无真正拥有CLCat
。所谓“真正”拥有,就是指只要CLPerson
还在,那么CLCat
就不该该被释放。为此,咱们就能够这么作
#import "CLPerson.h"
@implementation CLPerson
-(void)setCat:(CLCat *)cat {
[_cat retain];//将引用计数+1
_cat = cat;
}
-(CLCat *)cat {
return _cat;
}
-(void)dealloc {
//本身即将被释放,再也不须要cat了
[_cat release];
_cat = nil;
[super dealloc];
NSLog(@"%s",__func__);
}
@end
复制代码
这样即便有多个CLPerson
对象在使用CLCat
,也不会出问题了
int main(int argc, const char * argv[]) {
@autoreleasepool {
//RC+1
CLCat *cat = [[CLCat alloc] init];
CLPerson *person = [[CLPerson alloc] init];
CLPerson *person2 = [[CLPerson alloc] init];
//内部 RC+1(setCat) --> RC-1(dealloc)
[person setCat:cat];
//内部 RC+1(setCat) --> RC-1(dealloc)
[person2 setCat:cat];
//RC-1,为了对应上面的[CLCat alloc]
[cat release];
[person.cat run];
[person2.cat run];
[person release];
[person2 release];
}
return 0;
}
复制代码
从CLCat
的retainCount
变化过程可判断,最后它必定会变成0
,不影响CLCat
实例对象的释放,同时,也保证了CLCat
的retainCount
必定是在最后一个CLPerson
实例对象释放以前(意味着CLCat
再也不被须要了,能够被释放了)被变成0
,成功释放。运行结果也能够验证
2019-08-27 10:55:41.799859+0800 MRCManager[11120:657618] -[CLCat run]
2019-08-27 10:55:41.800096+0800 MRCManager[11120:657618] -[CLCat run]
2019-08-27 10:55:41.800111+0800 MRCManager[11120:657618] -[CLPerson dealloc]
2019-08-27 10:55:41.800117+0800 MRCManager[11120:657618] -[CLCat dealloc]
2019-08-27 10:55:41.800123+0800 MRCManager[11120:657618] -[CLPerson dealloc]
Program ended with exit code: 0
复制代码
总的来讲,手动管理内存的原则就是:保持实例对象的retainCount平衡,有+1,就有对应的-1,保证最终会变成0,实例对象能够被成功释放。
到目前为止,上面对setCat
方法的处理,仍然不够完善。接下来咱们继续讨论一下相关细节。 首先看下面的场景
int main(int argc, const char * argv[]) {
@autoreleasepool {
//person_rc + 1 = 1
CLPerson *person = [[CLPerson alloc] init];
//cat1_rc + 1 = 1
CLCat *cat1 = [[CLCat alloc] init];
//cat2_rc + 1 = 1
CLCat *cat2 = [[CLCat alloc] init];
//cat1_rc + 1 = 2
[person setCat:cat1];
//cat2_rc + 1 = 2
[person setCat:cat2];
//cat1_rc - 1 = 1
[cat1 release];
//cat2_rc - 1 = 1
[cat2 release];
//cat2_rc - 1 = 0
//person_rc - 1 = 0
[person release];
}
return 0;
}
**************** 打印结果 ****************
2019-08-27 11:23:20.185060+0800 MRCManager[11164:667802] -[CLCat dealloc]
2019-08-27 11:23:20.185318+0800 MRCManager[11164:667802] -[CLPerson dealloc]
Program ended with exit code: 0
复制代码
打印结果显示,cat1
产生了内存泄漏。根据代码注释里对各对象retainCount
的跟踪,能够看出,是由于person
在setCat
方法里设置cat2
为成员变量的时候,形成了cat1
最终少进行了一次release
,从而致使被泄漏。所以须要对setCat
方法调整以下便可
-(void)setCat:(CLCat *)cat {
[_cat release];//将以前_cat所指向的对象引用计数-1,不在持有
[cat retain];//将传进来的对象引用计数+1,保证持有
_cat = cat;
}
复制代码
上面咱们解决了用不一样CLCat
对象进行setCat
设置所产生的问题。接下来咱们还须要看看用同一个CLCat
对象进行setCat
设置是否安全,代码以下
int main(int argc, const char * argv[]) {
@autoreleasepool {
//person_rc + 1 = 1
CLPerson *person = [[CLPerson alloc] init];
//cat_rc + 1 = 1
CLCat *cat = [[CLCat alloc] init];
//cat_rc + 1 = 2
[person setCat:cat];
//cat_rc - 1 = 1
[cat release];
[person setCat:cat];
[person release];
}
return 0;
}
复制代码
上述代码能够安全走到下图所示的断点处咱们继续执行,就会在
setCat
方法里看到以下报错能够看到
[cat retain]
报了EXC_BAD_ACCESS
错误,说明cat
此时已经被释放了。咱们来分析一下,进入此方法是,cat
对象的retainCount
是1
,当再次把cat对象传setCat
方法是,因为person
的_cat
指向的也是cat
,所以[_cat release]
实际上就会致使cat
的retainCount-1
,也就是1-1=0
,因此cat
被系统释放。所以后面的代码再次使用[cat retain]
便形成了野指针错误。所以解决办法是须要对传如的cat
对象判断一下,若是等于当前_cat
,就不须要执行引用计数的操做了,修改代码以下
-(void)setCat:(CLCat *)cat {
if (cat != _cat) {//只有当传进来的对象跟当前对象不一样,才须要进行后面的操做
[_cat release];//将以前_cat所指向的对象引用计数-1,不在持有
[cat retain];//将传进来的对象引用计数+1,保证持有
_cat = cat;
}
}
复制代码
这样,对于setCat
方法的处理放啊就完善了。下面我贴一份完整的代码供参考
******************* CLPerson.h ******************
#import <Foundation/Foundation.h>
#import "CLCat.h"
@interface CLPerson : NSObject
{
CLCat *_cat;
int _age;
}
//拥有猫
-(void)setCat:(CLCat *)cat;
//获取猫
-(CLCat *)cat;
@end
******************* CLPerson.m ******************
#import "CLPerson.h"
@implementation CLPerson
-(void)setCat:(CLCat *)cat {
if (cat != _cat) {//只有当传进来的对象跟当前对象不一样,才须要进行后面的操做
[_cat release];//将以前_cat所指向的对象引用计数-1,不在持有
[cat retain];//将传进来的对象引用计数+1,保证持有
_cat = cat;
}
}
-(CLCat *)cat {
return _cat;
}
-(void)dealloc {
//本身即将被释放,再也不须要cat了
// [_cat release];
// _cat = nil;
self.cat = nil;//至关于上面两句的效果
[super dealloc];
NSLog(@"%s",__func__);
}
@end
*****************CLCat.h ***************
#import <Foundation/Foundation.h>
@interface CLCat : NSObject
-(void)run;
@end
*****************CLCat.m ***************
#import "CLCat.h"
@implementation CLCat
-(void)run {
NSLog(@"%s",__func__);
}
-(void)dealloc {
[super dealloc];
NSLog(@"%s",__func__);
}
@end
复制代码
对于非OC对象类型的成员变量,就不须要考虑内存管理的问题了,例如
@interface CLPerson : NSObject
{
int _age;
}
-(void)setAge:(int)age ;
-(int)age;
@end
@implementation CLPerson
-(void)setAge:(int)age {
_age = age;
}
-(int)age {
return _age;
}
@end
复制代码
上面过程当中,包含了非ARC时代进行手动内存管理的所有核心点。后来,随着Xcode的逐步发展,编译器自动帮咱们生成了不少代码,让个人代码书写更加简洁。
@property
+ @synthesize
@property (nonatomic, retain) cat;
的做用:自动声明getter
、setter
方法-(void)setCat:(CLCat *)age ; -(CLCat *)cat; 复制代码
@synthesize cat = _cat;
的做用:
- 为属性
cat
生成成员变量_cat
{ CLCat *_cat; } 复制代码
- 自动生成
getter
、setter
方法的实现-(void)setCat:(CLCat *)cat { if (cat != _cat) { [_cat release]; [cat retain]; _cat = cat; } } -(CLCat *)cat { return _cat; } 复制代码
@property
+ @synthesize
后来苹果更进一步,连
@synthesize
都不须要咱们写了,一个@property
搞定
- 成员变量的建立
getter
、setter
方法声明getter
、setter
方法实现
可是须要注意的是,@property
并不帮我作dealloc
里面的处理(对不须要使用的成员变量进行释放),所以dealloc
方法仍是须要咱们手动去写的。
好了,上古时期的MRC手动内存管理就介绍到这里。