美文网首页
iOS 内存管理进阶

iOS 内存管理进阶

作者: George_Luofz | 来源:发表于2018-04-01 12:42 被阅读38次
    1. 基础知识
    1. 可执行文件的内存结构
    • .bss区,未初始化的全局变量和静态变量
    • .data区,初始化的全局变量和静态变量
    • stack区,临时变量,函数
    • heap区,对象,在所有的线程,共享库,和动态加载的模块中被共享使用

    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 引用计数

    • alloc/init new copy mutableCopy会修改对象引用计数+1
    • release 会引用计数-1
    • autorelease 当autoreleasepool drain时,会对加入其中的对象调用release方法,然后引用计数-1
      1.3 MRC下property setter/getter方法
    - (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

    • alloc/init new copy mutableCopy 这些方法创建的都是retained return value,需要负责释放对象
    • 其他工厂方法创建的都是unretained return value,不需要负责释放对象,如[NSArray array]方法,原因是该方法返回的对象是autorelease的,由autoreleasepool负责释放
    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

    相关文章

      网友评论

          本文标题:iOS 内存管理进阶

          本文链接:https://www.haomeiwen.com/subject/kyvocftx.html