1.iOS内存
IOS 内存区主要可以划分为五个区,栈区、堆区、全局变量区、常量区、代码区。
栈区:系统负责内存管理,通常不需要程序员干预。一般局部变量,函数跳转的现场维护等都是在栈区处理。所以一些深度递归,函数的循环调用可能会造成内存飙升。
堆区:程序员主动申请且必须主动释放的内存区域,所以有malloc 必有对应的free 。否则也会造成内存泄露。
全局变量区:存放全局变量的区域,系统会在程序执行结束后释放其内存。
常量区:存放const 修饰的常量的区域。
代码区:存放代码的区域。
2.内存管理原理
IOS 内存管理的原理其实就是通过引用计数来控制内存的释放,当引用计数变为零的时候,系统会主动释放该对象所占用的内存,不管是MRC或者ARC也好,二者的区别无非就是MRC需要程序员去手动对对象的引用计数进行加减的操作,而ARC是系统帮我们对对象的引用计数进行加减的操作。
3.内存管理之ARC/MRC
MRC:程序员可以通过new ,alloc ,retain,copy ,mutableCopy 等关键字来给对象的引用计数执行+1的操作,也可以通过release ,autorelease来给对象的引用计数执行-1的操作。(Strong 关键字只用于ARC,相当于MRC中的retain。autorelease作用与release 类似,相当于一个延迟的release ,在自动释放池结束时执行一次release 。 )
ARC:综上所述,ARC就是系统帮我们对对象的引用计数进行加减的操作,那系统依据什么来帮我们进行这个操作呢,答案就是关键字,关键字决定了对象的所有权修饰符。
一共有4种所有权修饰符:___strong ,__weak ,__unsafe_unretained ,__autoreleasing.
strong:(对应的所有权类型是__strong)强引用对象,懒加载的view需要使用strong 修饰,一般的对象用该关键字,NSMutableArray,NSMutableDictionary用该关键字修饰
weak:(对应的所有权类型是__weak)弱引用对象,一般非懒加载的视图用weak 修饰,ARC中代理也用weak 修饰
assign:(对应的所有权类型是__unsafe_unretained)一般用于修饰基本数据类型,MRC中的代理也用assign修饰
copy :(对应的所有权类型是__strong)一般字符串和NSArray,NSDictionary以及block用copy 修饰
3.自动释放池
自动释放池在排干的时候(结束的时候)会向加入到其中的对象发送一条release 消息,如果内存控制得当,其中的对象在收到这条release 消息时应该是正好能够释放,否则就会出现内存泄露或者崩溃的情况(过度释放就会导致崩溃,释放次数不够就会导致内存泄露)。所以综上所述,对象在加入到自动释放池中时会收到一条autoRelease消息,但并不是马上释放,而是要等到自动释放池排干的时候,自动释放池再向其中的对象发送release 消息,从而得到释放。也就是说,自动释放池有延迟释放的功能。一般情况下,我们不需要自己主动创建自动释放池,因为系统已经为我们创建好了。那什么时候需要我们主动创建自动释放池呢?一个经典的场景就是:在一个次数较大的循环中,每次都会创建临时对象,而这个临时对象又是比较占内存的,比如图片对象。按照规则,只有到这个循环全部结束时,系统才会释放该循环中创建的对象,这样很明显就会造成内存暴涨甚至崩溃。这个时候,我们只需要在循环中每次使用一个临时自动释放池,就能使图片对象提前得到释放了。
4.内存泄漏或内存不当释放的场景
4.1 关键字的不合理使用:代理使用了strong ,非懒加载的视图使用了strong ,NSMutableArray或NSMutableDictionary使用了copy ,
4.2 block:这是造成内存问题的重灾区,首先很容易造成的循环信用,就是很多对象持有了block ,然后block 中又引用了self 所造成的,一般可以在block 中使用一个weakSelf来解决。但这时候就有另外一个场景需要注意了,就是这个weakSelf有可能在block 执行过程中就释放了而造成野指针访问,所以老司机一般都使用weakSelf-strongSelf dance 。
4.3 单例的滥用
4.4 NSTimer
使用完没有invalidate,但NSTimer的使用方式还是需要考究,目前主流的解决方案是通过runtime动态添加中间类和方法,然后再dealloc中对NSTimer进行invalidate和置空的操作。这样不仅避免了控制器直接使用定时器时造成的循环引用,而且也避免了当跳转到下个控制器还需返回时对定时器的不当释放,然后后续又需重新创建定时器带来的开销。
4.5 NSNotification
没有及时remove
4.6 Observer
没有及时remove
4.7 CF类型的内存
一般使用CG,CF开头的方法创建的对象,都需要手动的调用相对应的release来进行释放。
4.8 performSelector
使用performSelector时会造成对象的引用计数+1,直到控制器执行viewDidDisappear时才会对应的-1。如果这个时候还有没来得及执行的方法,就需要调用在viewDidDisappear中添加调用[NSObject cancelPreviousPerformRequestsWithTarget:self],取消后续的方法的执行。
4.9 malloc申请的内存
也就是在堆上申请的内存,未主动释放会造成内存泄漏。
4.10 数据库、connettion、socket用完没有关闭,文件打开没有关闭
5.内存优化
5.1 适当的地方使用reuseIdentifier,合理的重用视图
5.2 尽可能设置视图不透明
5.3 避免臃肿的XIB
5.4 调整图片尺寸,使得适合视图中的图片框尺寸
5.5 考虑绘图
5.6 合理使用imageNamed和imageWithContentOfFile
5.7 选择合适的方式设置背景图
(1)UIColor的colorWithPatternImage:
(2)UIImageView:全尺寸的背景图片时使用
5.8 使用自动释放池
针对大循环中需要创建很多的临时变量,而该临时变量又是较占内存的场景
5.9 尽量避免使用NSDateFormatter
6.与内存相关的典型问题解析
6.1 为什么分类中不能新增属性,但能新增方法
答:因为类的内存在其编译的时候就已经确定,所以不能增加属性。但新增方法不会增加内存,所以是可以的。
6.2 如何处理加载大量图片时导致的内存问题
答:1.如果使用的是阿里云,可以直接设置取缩略图片 2.本地压缩处理 3.如果使用的SDWebImage,可以在适当时候调用[[SDImageCache sharedImageCache] setValue:nil forKey:@"memCache"];
6.3 NSMutableArray能用copy修饰吗,为什么?
答:不可以,用copy修饰的数组会变成不可变数组,对其进行增删改查就会崩溃。同理,NSMutabDictionary,NSMutableString也不能用copy修饰,而应该用strong修饰,因为我们使用可变类型要使用的就是它的可变属性,使用copy就将它变为不可变类型了。