iOS 内存管理进阶

2018-04-01  本文已影响38人  George_Luofz
1. 基础知识
  1. 可执行文件的内存结构

1.1 全局变量和static变量区别
(static分两种,函数外或者函数内)

  1. 主要在作用域上,全局变量不仅可以在当前文件中使用,static函数外类型只能在当前文件使用
  2. static函数内(外也一样)变量会保存上一次修改的值

1.2 extern关键字
表示其他文件可以引用,与static意义正好相反
1.3 栈和堆内存分配与释放

名称 地址连续 分配/释放者 大小
是一块连续内存,不会出现内存碎片 编译器分配/释放,效率较高 最大容积固定
不连续内存,容易出现内存碎片 由程序员负责分配/释放,容易内存泄漏 最大容积受虚拟内存影响

1.4 内存分配

  1. 虚拟地址 将程序代码或者数据分成若干个段(),每个段都有段名称+段内相对地址
  2. 逻辑地址 段内相对地址(相对概念)
  3. 物理地址 顾名思义,虚拟地址,在内存条上的物理映射

1.5 页面置换算法
LRU、FIFO

上边内容参考:内存管理


2. iOS 内存管理知识
  1. MRC
  2. ARC
  3. 属性关键字
  4. 变量关键字
  5. autoreleasepool
  6. dealloc
  7. 检测内存泄漏
3. 详细说明
  1. MRC
    1.1 原则:谁创建谁释放,谁引用释放
    (换句话说,谁修改了对象的引用计数,谁就负责释放)
NSObject *a = [[NSObject alloc] init]; //引用计数为1
[a release];

1.2 引用计数

- (void)setData:(NSData *)data{
  if(_data != data){  //判断是不同对象再release,否则可能会先release,导致野指针
    [_data release]; // 释放之前对象
    _data = [data retain]; // 引用新对象,引用计数+1
  }
}

- (NSData *)data{
  return [[_data retain] autorelease]; //调用retain保证对象不被释放,调用autorelease保证对象在合适时机释放,这样调用方不需要负责释放
}

1.4 使用与不使用临时变量区别

@property (nonatomic, retain) NSObject  *property; 
...
NSObject * a = [[NSObject alloc] init]; 
self.property = a;
[a release]; //1. 对象执行结束后,对象引用计数为1

self.property = [[NSObject alloc] init]; // 2. 语句执行完毕后,对象引用计数为2

...
- (void) dealloc{
[_property release]; //若为1情况,release之后,引用计数为0,正常释放;若为2情况,release之后,引用计数为1,无法释放
_property = nil; 
}

1.5 retained return value和unretained return value

  1. ARC
    懂了MRC,ARC就非常容易理解,无非就是原来需要手动写release的地方,由编译器在编译时帮我们写了,让我们专注于业务代码,不care释放问题
    2.1. 知道编译器插入了什么代码
    (这个地方蛮复杂的,因为还有很多优化),可以考虑在MRC下需要怎么释放,然后编译器就加了那部分代码
NSArray *array = [[NSArray alloc] init];
self.property = array;
// [array release]; //编译器会加入该行代码

2.2 ARC下函数的返回值
此处可参考clang文档: retained-return-values
总结来说就是,对于retained return value,其实就是MRC下alloc/init new copy mutableCopy这些方法创建的对象,编译器会retain,然后再release
对于unretained return value,编译器会做优化,在最坏的情况下使用autorelease,正常情况就是延长生命周期,然后在合适时间释放。
(此处解释不太透彻,需要生成编译之后源码,直接看源码才能清晰)

3. 属性关键字

3.1 总共有6个,分别是:
assign weak unsafe_unretained
retain strong copy
3个不修改引用计数的,3个修改引用计数的(copy也没有,但是有一个新的指针,可以理解成多了个引用,其实引用计数没变)
3.2 几个区别
3.2.1 weak 与 assign unsafe_unretained
都不修改引用计数,
weak对象,当对象引用计数为0时,指向该对象的指针变量会自动为nil,不会出现野指针,unsafe_unretained不会自动置为nil
assign 还可以修饰基础数据类型,其他两个不可以
3.2.2 strong 与 retain copy
strong与retain基本是一样的,只有一个区别
strong修饰的block会被copy,retain修饰的不会
copy 会生成一个新的指针变量指向原对象,一般NSString常用

4. 变量关键字[ARC下有效]

有四个,__unsafe__unretained __weak __strong __autoreleasing (__block特殊)
4.1 __strong,默认关键字,不过不会修改对象引用计数,只是表示一种owner关系

NSMutableArray * str = [[NSMutableArray alloc] init]; //不会崩溃,默认__strong
NSMutableArray * __unsafe_unretained str = [[NSMutableArray alloc] init]; //会崩溃,没有

4.2 __autoreleasing
一般用在二级指针和id *类型的对象(其实还是二级指针)
如NSError ** __autoreleasing error; 一般作为函数参数声明使用
(此处理解还不透彻)
4.3 __unsafe__unretained
能分析透彻下边代码;
{}表示,代码只在{}域内有效

id __unsafe_unretained obj1 = nil;
//    @autoreleasepool{ //打开autoreleasepool会崩溃
    
        {
            //        id  obj0 = [[NSMutableArray alloc] init];      //会崩溃
            //[NSMutableArray array] 会崩溃原因是:
            //objc_autoreleaseReturnValue函数会检查使用该函数的方法或函数调用方的执行命令列表,如果方法或函数的调用方在调用了方法或函数后紧接着调用objc_retainAutoreleasedReturnValue函数,那么就不将返回的对象注册到autoreleasepool中,而是直接传递到方法或函数的调用方
            //                id  obj0 = [NSMutableArray array]; // 会崩溃
            id  obj0 = [NSMutableArray arrayWithObjects:@"234", nil]; //不会崩溃,内部有retain
            
            [obj0 addObject:@"obj"];
            obj1 = obj0;
            NSLog(@"obj0 = %@", obj0);
        }
//    }
5. autoreleasepool

5.1 对象释放时机
出了{}域;
runloop每次循环结束
5.2 函数返回值
函数的返回值一般都是autorelease的
5.3 好处
在ARC下,可以让对象更快释放,其实相当于在{}内对每个对象调用了release方法,不用等到runloop走完再释放了
@autoreleasepool对[[obj alloc] init]生成的对象无效,因为只有[obj autorelease]的对象才会加入autoreleasepool
5.4 与指针置为nil对比
指针置为nil并不会释放对象,只是把指针变量置为空了,对象引用计数并没有受到影响

6. dealloc

6.1 变量置为nil
ARC下dealloc中一般是不用手动把对象置为nil的,编译器自动帮我们做了
6.2 ARC下dealloc中不要调super
MRC下[super dealloc]放在最下边一行,先让子类对象释放完毕
6.3 MRC下dealloc中变量释放使用实例变量
不要使用[self.property release]这种方式,因为防止父类和子类都重写了setter方法,那么子类中先释放,子类变成空,到父类释放时,还是要调用子类的setter方法,这时候子类已经不存在了。[经过我验证,这个情况没有发生,以后再探究]

7. 检测内存泄漏思路

7.1 使用工具
使用Leaks检测释放的内存,野指针之类
使用Allocations检测废弃的内存,有些无用的对象内存没有释放。[这个还没用过]
7.2 常见内存泄漏情况[我遇到的一些]
调用c方法时,要尤其注意

  1. runtime操作,class_copyIvarList方法,用完后未调用free
  2. 在类方法中,使用[self alloc] init],换成类名
  3. 使用sqlite3_exec方法,调用sqlite3_free方法
  4. 使用NSUrlSession,在completionBlock中或者comlete delegate中,调用finishTaskAndInvalidate方法
  5. NSTimer及时关闭
  6. performSelector
  7. oc和c互调

补充的一些,参考LNLeaksFinder

  1. NSNotificationCenter Block

7.3 代码检测的思路
得借用runtime,我想想

参考:
内存管理
引用计数原理
iOS内存泄漏自动检测工具PLeakSniffer

上一篇下一篇

猜你喜欢

热点阅读