一、内存管理(MRC)
(一) 管理对象
管理对象:OC对象
原因:
1、OC对象存放于堆里面
2、非OC对象(int、char、float、double、struct、enum)一般放在栈里面(栈内存会被系统自动回收)
(二) 基本原理
1、每个OC对象内部都有4个字节的存储空间来存放引用计数器
2、谁创建谁release :如果你通过alloc、new或[mutable]copy来创建一个对象(引用计数+1),那么你必须调用release或autorelease(引用计数-1)
3、谁retain谁release:只要你调用了retain(引用计数+1),就必须调用一次release(引用计数-1)
4、release并不代表销毁\回收对象,仅仅是计数器-1;引用计数为0时,对象被销毁,系统会自动给对象发送一条dealloc消息。
5、一般来说,除了alloc、new或copy之外的方法创建的对象都被申明了autorelease。
(三) Set方法
- (void) setName:(NSString *)name
{
if(_name!= name){
[_namerelease];
_name= [name retail];
}
}
(四) 一些术语
1、僵尸对象
已经被销毁的对象(不能再使用的对象)
2、野指针
指向僵尸对象(不可用内存)的指针
给野指针发消息会报EXC_BAD_ACCESS错误
3、空指针
没有指向存储空间的指针(里面存的是nil,也就是0)
给空指针发消息是没有任何反应的
objc_msgSend会通过判断self来决定是否发送消息,如果self为nil,那么selector也会为空,直接返回,所以不会出现问题。视方法返回值,向nil发消息可能会返回nil(返回值为对象)、0(返回值为一些基础数据类型)或0X0(返回值为id)等。但是对[NSNull null]对象发送消息时,是会crash的,因为这个NSNull类只有一个null方法
为了避免野指针错误的常见办法
在对象被销毁之后,将指向对象的指针变为空指针
(五) 一些设置
1、关闭ARC功能
Building->AutoMatic Reference Counting->NO
2、开启僵尸对象监控
Edit Scheme->Diagnostics->Enable Zombie Object
(六) NSString创建类的几种方式
NSString类型的字符串有三种方法:
方法1.直接赋值: NSString *str1= @"my string";
方法2.类函数初始化生成: NSString *str2= [NSString stringWithString:@"my string"];
方法3.实例方法初始化生成: NSString *str3= [[NSString alloc] initWithString:@"my string"];
NSString *str4 =[[NSString alloc]initWithFormat:@"my string"];
区别1:方法一生成字符串时,不会初始化内存空间,所以使用结束后不用释放内存;
而其他三个都会初始化内存空间,使用结束后要释放内存;
在释放内存时方法2和3也不同,方法2是autorelease类型,内存由系统释放;方法3则必须手动释放
区别2:用Format初始化的字符串,需要初始化一段动态内存空间,如:0x6a42a40;
而用String声明的字符串,初始化的是常量内存区,如:0x46a8,常量内存区的地址,只要值相同,占用的 地址空间是一致的。
所以str3和str1的地址一致,但是str4和str1的地址不一致。
二、内存警告
(一) app收到Memory Warning后会调用:
UIApplication::didReceiveMemoryWarning->
UIApplicationDelegate::applicationDidReceiveMemoryWarning,
然后调用当前所有的viewController进行处理。
因此处理的主要工作是在viewController。
(二) 当我们的程序在第一次收到内存不足警告时,应该释放一些不用的资源,以节省部分内存。否则,当内存不足情形依然存在,iOS再次向我们程序发出内存不足的警告时,我们的程序将会被iOS kill掉。
1、iOS3-iOS5.0以前版本收到内存警告:
调用didReceiveMemoryWarning内调用super的didReceiveMemoryWarning会将controller的view进行释放。所以我们不能将controller的view再次释放。
处理方法:
-(void)didReceiveMemoryWarning{
[super didReceiveMemoryWarning];//如没有显示在window上,会自动将self.view释放。
// ios6.0以前,不用在此做处理,self.view释放之后,会调用下面的viewDidUnload函数,在viewDidUnload函数中做处理就可以了。
}
-(void)viewDidUnload{
// Release any retained subviews of the main view.不包含self.view
//处理一些内存和资源问题。
[super viewDidUnload];
}
2、iOS6.0及以上版本的内存警告:
调用didReceiveMemoryWarning内调用super的didReceiveMemoryWarning
只是释放controller的resouse,不会释放view
处理方法:
-(void)didReceiveMemoryWarning{
[super didReceiveMemoryWarning];//即使没有显示在window上,也不会自动的将self.view释放。
// Add code to clean up any of yourown resources that are no longer necessary.
//此处做兼容处理需要加上ios6.0的宏开关,保证是在6.0下使用的,6.0以前屏蔽以下代码,否则会在下面使用self.view时自动加载viewDidUnLoad
if ([[UIDevicecurrentDevice].systemVersion floatValue] >= 6.0) {
//需要注意的是self.isViewLoaded是必不可少的,其他方式访问视图会导致它加载,在WWDC视频也忽视这一点。
if (self.isViewLoaded &&!self.view.window)//是否是正在使用的视图{
// Add code to preserve data storedin the views that might be
// needed later.
// Add code to clean up other strongreferences to the view in
// the view hierarchy.
self.view = nil;//目的是再次进入时能够重新加载调用viewDidLoad函数。
}
}
}
但是似乎这么写相对于以前并不省事。最终我们找到一篇文章,文章中说其实并不值得回收这部分的内存,原因如下:
1. UIView是UIResponder的子类,而UIResponder有一个CALayer的成员变量,CALayer是具体用于将自己画到屏幕上的。
2. CALayer是一个bitmap图象的包装类,当UIView调用自身的drawRect时,CALayer才会创建这个bitmap图象类。
3.具体占内存的其实是一个bitmap图象类,CALayer只占48bytes, UIView只占96bytes。而一个iPad的全屏UIView的bitmap类会占到12M的大小!
4.在iOS6时,当系统发出MemoryWarning时,系统会自动回收bitmap类。但是不回收UIView和CALayer类。这样即回收了大部分内存,又能在需要bitmap类时,根据CALayer类重建。
所以,iOS6这么做的意思是:我们根本没有必要为了几十byte而费力回收内存。
生命周期图
三、内存泄漏分析解决方法
(一) 静态分析
通过静态分析我们可以最初步的了解到代码的一些不规范的地方或者是存在的内存泄漏,这是我们第一步对内存泄漏的检测。当然有一些警告并不是我们关心的可以略过。
(二) 通过instruments来检查内存泄漏
这个方法能粗略的定位我们在哪里发生了内存泄漏。方法是完成一个循环操作,如果内存增长为0就证明我们程序在该次循环操作中不存在内存泄漏,如果内存增长不为0那证明有可能存在内存泄漏,当然具体问题需要具体分析。
(三) 代码测试内存泄漏
在做这项工作之前我们要注意一下,在dealloc的方法中我们是否已经释放了该对象所拥有的所有对象。观察对象的生成和销毁是否配对。准确的说就是init(创建对象的方法)和dealloc是否会被成对触发(简单说来就是走一次创建对象就有走一次dealloc该对象)。
四、内存检查工具
编译和分析工具Analyze
内存泄漏检测工具—Leak
内存猛增检测工具—Allocations