美文网首页
iOS内存管理

iOS内存管理

作者: 码省理工0 | 来源:发表于2019-03-22 08:07 被阅读0次

    内存中的五大区域
    引用计数
    ARC MRC
    属性所有权
    僵尸对象、野指针、空指针分别指什么,有什么区别?

    内存中的五大区域

    内存分为5个区域,分别指的是----->栈区/堆区/BSS段/数据段/代码段

    栈:存储局部变量,当其作用域执行完毕之后,就会被系统立即收回

    堆:存储OC对象,手动申请的字节空间,需要调用free来释放

    BSS段:未初始化的全局变量和静态变量,一旦初始化就会从BSS段中回收掉,转存到数据段中

    数据段:存储已经初始化的全局变量和静态变量,以及常量数据,直到结束程序时才会被立即收回

    代码段:代码,直到结束程序时才会被立即收回

    引用计数

    引用计数(Reference Count)是一个简单而有效的管理对象生命周期的方式。当我们创建一个新对象的时候,它的引用计数为 1,当有一个新的指针指向这个对象时,我们将其引用计数加 1,当某个指针不再指向这个对象是,我们将其引用计数减 1,当对象的引用计数变为 0 时,说明这个对象不再被任何指针指向了,这个时候我们就可以将对象销毁,回收内存。


    Snip20190321_13.png

    内存管理的思考方式

    • 自己生成的对象,自己持有
    • 不是自己生成的对象,自己也能持有
    • 不再需要自己持有的对象时就释放
    • 非自己持有的对象无法释放

    自己生成的对象,自己持有

    关键字 allocnewcopymutableCopy

    /*
     * 自己生成并持有该对象
     */
     id obj0 = [[NSObeject alloc] init];
     id obj1 = [NSObeject new];
    

    不是自己生成的对象,自己也能持有

    /*
     * 持有非自己生成的对象
     */
    id obj = [NSArray array]; // 非自己生成的对象,且该对象存在,但自己不持有
    
    [obj retain]; // 自己持有对象
    
    

    不再需要自己持有的对象时就释放

    /*
     * 不在需要自己持有的对象的时候,释放
     */
    id obj = [[NSObeject alloc] init]; // 此时持有对象
    
    [obj release]; // 释放对象
    /*
     * 指向对象的指针仍就被保留在obj这个变量中
     * 但对象已经释放,不可访问
     */
    
    

    非自己持有的对象无法释放

    /*
     * 非自己持有的对象无法释放
     */
    id obj = [NSArray array]; // 非自己生成的对象,且该对象存在,但自己不持有
    
    [obj release]; // ~~~此时将运行时crash 或编译器报error~~~ 非 ARC 下,调用该方法会导致编译器报 issues。此操作的行为是未定义的,可能会导致运行时 crash 或者其它未知行为
    
    

    非自己生成的对象,且该对象存在,但自己不持有

    - (id) getAObjNotRetain {
        id obj = [[NSObject alloc] init]; // 自己持有对象
        
        [obj autorelease]; // 取得的对象存在,但自己不持有该对象
        
        return obj;
    }
    

    我们使用了autorelease方法,用该方法,可以使取得的对象存在,但自己不持有

    Snip20190321_14.png
    [NSMutableArray array] [NSArray array]都可以取得谁都不持有的对象,这些方法都是通过autorelease实现的。

    ARC

    自动的引用计数(Automatic Reference Count 简称 ARC),是苹果在 WWDC 2011 年大会上提出的用于内存管理的技术,让编译器来进行内存管理。在新一代Apple LLVM编译器中设置ARC为有效状态,就无需再次自己写retain,release代码,降低了程序崩溃,内存泄漏风险,同时也减少开发者的工作量。
    所有权限修饰符

    • __strong
    • __weak
    • __unsafe_unretained
    • __autoreleasing

    说到变量所有权修饰符,有人可能会跟属性修饰符搞混,这里做一个对照关系小结:

    • assign 对应的所有权类型是 __unsafe_unretained。

    • copy 对应的所有权类型是 __strong。

    • retain 对应的所有权类型是 __strong。

    • strong 对应的所有权类型是 __strong。

    • unsafe_unretained对应的所有权类型是__unsafe_unretained。

    • weak 对应的所有权类型是 __weak。

    除了weak这些修饰符之外,这些修饰符在非ARC模式下可用。

    __strong

    __strong 表示强引用,对应定义 property 时用到的 strong。当对象没有任何一个强引用指向它时,它才会被释放。如果在声明引用时不加修饰符,那么引用将默认是强引用。当需要释放强引用指向的对象时,需要保证所有指向对象强引用置为 nil。__strong修饰符是id类型和对象类型默认的所有权修饰符。

    {
        id __strong obj = [[NSObject alloc] init];
    }
    //编译器的模拟代码
    id obj = objc_msgSend(NSObject,@selector(alloc));
    objc_msgSend(obj,@selector(init));
    // 出作用域的时候调用
    objc_release(obj);
    

    虽然ARC有效时不能使用release方法,但由此可知编译器自动插入了release。

    对象是通过除alloc、new、copy、multyCopy外方法产生的情况

    {
        id __strong obj = [NSMutableArray array];
    }
    
    //编译器的模拟代码
    id obj = objc_msgSend(NSMutableArray,@selector(array));
    objc_retainAutoreleasedReturnValue(obj);
    objc_release(obj);
    

    objc_retainAutoreleasedReturnValue函数主要用于优化程序的运行。它是用于持有(retain)对象的函数,它持有的对象应为返回注册在autoreleasePool中对象的方法,或是函数的返回值。像该源码这样,在调用array类方法之后,由编译器插入该函数。
    而这种objc_retainAutoreleasedReturnValue函数是成对存在的,与之对应的函数是objc_autoreleaseReturnValue。它用于array类方法返回对象的实现上。下面看看NSMutableArray类的array方法通过编译器进行了怎样的转换:

    + (id)array
    {
        return [[NSMutableArray alloc] init];
    }
    
    //编译器模拟代码
    + (id)array
    {
        id obj = objc_msgSend(NSMutableArray,@selector(alloc));
        objc_msgSend(obj,@selector(init));
        
        // 代替我们调用了autorelease方法
        return objc_autoreleaseReturnValue(obj);
    }
    

    我们可以看见调用了objc_autoreleaseReturnValue函数且这个函数会返回注册到自动释放池的对象,但是,这个函数有个特点,它会查看调用方的命令执行列表,如果发现接下来会调用objc_retainAutoreleasedReturnValue则不会将返回的对象注册到autoreleasePool中而仅仅返回一个对象。达到了一种最优效果。如下图:

    image

    __weak

    __weak 表示弱引用,对应定义 property时用到的 weak。弱引用不会影响对象的释放,而当对象被释放时,所有指向它的弱引用都会自定被置为 nil,这样可以防止野指针。使用__weak修饰的变量,即是使用注册到autoreleasePool中的对象。__weak 最常见的一个作用就是用来避免循环循环。需要注意的是,__weak 修饰符只能用于 iOS5 以上的版本,在 iOS4 及更低的版本中使用 __unsafe_unretained 修饰符来代替。
    __weak 的几个使用场景:

    • 在 Delegate 关系中防止循环引用。
    • 在 Block 中防止循环引用。
    • 用来修饰指向由 Interface Builder 创建的控件。比如:@property (weak, nonatomic) IBOutlet UIButton *testButton;。
    {
        id __weak obj = [[NSObject alloc] init];
     }
    

    编译器转换后的代码如下:

        id obj;
        id tmp = objc_msgSend(NSObject,@selector(alloc));
        objc_msgSend(tmp,@selector(init));
        objc_initweak(&obj,tmp);
        objc_release(tmp);
        objc_destroyWeak(&object);
    

    根据我们的知识,可以知道NSObject对象在生成之后立马就会被释放,其主要原因是__weak修饰的指针没有引起对象内部的引用计数器的变化
    因此,__weak修饰的指针常用于打破循环引用或者修饰UI控件,关于__weak修饰的指针引用场景这里不叙述,下面主要介绍其原理
    原理
    我们知道弱指针有两个作用:
    一. 修饰的指针不会引起指向的对象的引用计数器变化
    二. 当指向的对象被销毁时,弱指针全部置为nil, 那么除了这些之外,我们还有一个要说的就是,为什么我们在程序中不能频繁的使用weak呢?

    为什么弱指针不会引起指向的对象的引用计数器发生变化

    通过objc_initweak函数初始化含有__weak修饰的变量,在变量作用域结束时通过objc_destroyWeak
    对于__weak内存管理也借助了类似于引用计数表的散列表,它通过对象的内存地址做为key,而对应的__weak修饰符变量的地址作为value注册到weak表中,在上述代码中objc_initweak就是完成这部分操作,而objc_destroyWeak
    则是销毁该对象对应的value。当指向的对象被销毁时,会通过其内存地址,去weak表中查找对应的__weak修饰符变量,将其从weak表中删除。所以,weak在修饰只是让weak表增加了记录没有引起引用计数表的变化。
    对象通过objc_release释放对象内存的动作如下:

    • objc_release
    • 因为引用计数为0所以执行dealloc
    • _objc_rootDealloc
    • objc_dispose
    • objc_destructInstance
    • objc_clear_deallocating

    而在对象被废弃时最后调用了objc_clear_deallocating,该函数的动作如下:

    • 从weak表中获取已废弃对象内存地址对应的所有记录
    • 将已废弃对象内存地址对应的记录中所有以weak修饰的变量都置为nil
    • 从weak表删除已废弃对象内存地址对应的记录
    • 根据已废弃对象内存地址从引用计数表中找到对应记录删除

    据此可以解释为什么对象被销毁时对应的weak指针变量全部都置为nil,同时,也看出来销毁weak步骤较多,如果大量使用weak的话会增加CPU的负荷。

    还需要确认一点是:使用__weak修饰符的变量,即是使用注册到autoreleasePool中的对象。

    {
            id __weak obj1 = obj; 
            NSLog(@"obj2-%@",obj1);
        }
    

    编译器转换上述代码如下:

     id obj1; 
     objc_initweak(&obj1,obj);
    id tmp = objc_loadWeakRetained(&obj1);
    objc_autorelease(tmp);
    NSLog(@"%@",tmp);
    objc_destroyWeak(&obj1);
    

    objc_loadWeakRetained函数获取附有__weak修饰符变量所引用的对象并retain, objc_autorelease函数将对象放入autoreleasePool中,据此当我们访问weak修饰指针指向的对象时,实际上是访问注册到自动释放池的对象。因此,如果大量使用weak的话,在我们去访问weak修饰的对象时,会有大量对象注册到自动释放池,这会影响程序的性能。

    推荐方案 : 要访问weak修饰的变量时,先将其赋给一个strong变量,然后进行访问

    __unsafe_unretained

    作用

    __unsafe_unretained作用需要和weak进行对比,它也不会引起对象的内部引用计数器的变化,但是,当其指向的对象被销毁时__unsafr_unretained修饰的指针不会置为nil。而且一般__unsafe_unretained就和它的名字一样是不安全,它不纳入ARC的内存管理

    __autoreleasing

    将对象赋值有__autoreleasing修饰符的变量等同于ARC无效时调用对象的autorelease方法。

    @autoeleasepool {
            // 如果看了上面__strong的原理,就知道实际上对象已经注册到自动释放池里面了 
            id __autoreleasing obj = [[NSObject alloc] init];
        }
    

    编译器转换上述代码如下:

        id pool = objc_autoreleasePoolPush(); 
        id obj = objc_msgSend(NSObject,@selector(alloc));
        objc_msgSend(obj,@selector(init));
        objc_autorelease(obj);
        objc_autoreleasePoolPop(pool);
    
    @autoreleasepool {
            id __autoreleasing obj = [NSMutableArray array];
        }
    

    编译器转换上述代码如下:

      id pool = objc_autoreleasePoolPush();
      id obj = objc_msgSend(NSMutableArray,@selector(array));
      objc_retainAutoreleasedReturnValue(obj);
      objc_autorelease(obj);
      objc_autoreleasePoolPop(pool);
    

    上面两种方式,虽然第二种持有对象的方法从alloc方法变为了objc_retainAutoreleasedReturnValue函数,都是通过objc_autorelease,注册到autoreleasePool中。

    僵尸对象、野指针、空指针分别指什么,有什么区别?

    僵尸对象:占用空间被释放的对象

    野指针:指向僵尸对象的指针(给野指针发消息会报错)

    空指针:指向nil的指针(给空指针发消息会不报错)

    因为给野指针发消息会报错,因此我们要监听僵尸对象,这样就可以在控制台输出错误原因

    设置如下图


    image

    相关文章

      网友评论

          本文标题:iOS内存管理

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