美文网首页
内存管理

内存管理

作者: babyloveblues | 来源:发表于2019-08-05 19:05 被阅读0次

    第一部分 自动引用计数

    1.1 什么是自动引用计数

    Auto Reference Counting (ARC)

    1.2 内存管理 / 引用计数

    现实问题类比 - 办公室开灯关灯

    内存管理的思考方式

    OC中管理内存的方法不包含在语言中,而是包含在cocoa框架中,由NSObject类来负担内存管理的职责。
    (1)最早进入办公室的人开灯。
    (2)之后进入的人,需要照明。
    (3)下班离开的人不需要照明。
    (4)最后离开办公室的人关灯(此时此人无人需要照明)。

    照明设备操作 OC 对象动作
    开灯 生成对象
    需要照明 持有对象
    不需要照明 释放对象
    关灯 废弃对象
    对象操作 OC 方法
    生成并持有对象 alloc/new/copy/mutablecopy等方法
    持有对象 retain方法
    释放对象 release方法
    废弃对象 dealloc

    内存管理的思考方式

    • 自己生成的对象,自己所持有
        // 自己生成并持有对象
        
        id obj = [[NSObject alloc] init];
        
        // 自己持有对象
    
    • 非自己生成的对象,自己也能持有
        // 取得非自己生成并且持有的对象
    
        id obj = [NSMutableArray array];
        
        // 取得对象存在,但是不持有对象
        
        [obj retain];
        
        // 自己持有对象
    
    
    • 不再需要自己所持有的对象时释放
        // 自己生成并且持有对象
        
        id obj = [[NSObject alloc] init];
        
        // 自己持有对象
        
        [obj release];
        
        // 释放对象
        // 对象一经释放,对象不可再被访问
    
    
    • 非自己持有的对象无法释放

    (1)自己生成,自己持有

        // 自己生成并持有对象
        id obj = [[NSObject alloc] init];
        
        // 自己持有对象
        
        [obj release];
        
        // 对象已经释放
        
        [obj release];
        
        // 释放之后再次释放已非自己所持有的对象,应用程序崩溃
        // 再度废弃已经废弃了的对象的时候崩溃,访问已经废弃的对象时崩溃
    

    (2)取得对象存在,但自己不持有对象

        id obj1 = [obj0 object];
        
        // 取得对象存在,但自己不持有对象
    
        [obj1 release];
        
        // 释放了非自己持有的对象,肯定会导致应用程序的崩溃
    

    使用某个方法生成对象

    在这个方法中,我们使用了autorelease方法,使用该方法,可以取得对象的存在,但自己不持有队形,autorelease提供这样的功能,是对象在超出指定生存范围时候能够正确的释放(调用release方法)。

    如果一个对象调用release方法,该对象在内存中立即被释放
    如果一个对象调用autorelease方法,不立即释放,而是注册到autoreleasepool中,pool结束的时候会自动调用release。

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

    alloc/retain/release/dealloc的实现(GNUSetup(源代码参考)/Cocoa(追溯))

    GNUSetup框架是开源框架,也是Cocoa框架的可互换框架

    • GNUSetup的实现方法是把引用计数放在该类的存储位置的头部,去记录引用信息;(好处:少量代码即可完成 / 能够统一管理引用计数内存块和对象用内存块)
    • Cocoa则是采用散列表(引用计数表)来管理引用计数。(对象用内存块的分配无需考虑内存快头部 / 引用计数表存有内存块地址,可从各个记录追溯到各对象的内存块)

    autorelease

    类似于C语言中局部变量的特性(在C语言中,程序执行时,若某自动变量超出其作用域,该变量将被自动废弃)。

    autorealease会像C语言那样对待对象实例,当超出其作用域(相当于变量作用域)时,对象实例的release实力方法被调用,另外和C语言不同的是,编程人员可以设定变量的作用域。
    源代码表示如下

       NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        
        id obj = [[NSObject alloc] init];
        
        [obj autorelease];
        
        [pool drain];
    

    NSRunloop每次循环的过程中NSAutorealeasePool对象被生成或废弃。
    尽管如此,但在大量产生autorelease的对象时,只要是不放弃autoreleasePool对象,那么生成的autoreleasepool对象就不能被释放,因此会产生内存不足的现象。一个典型的例子,图像文件独如刀NSData对象,并从中生成UII看嘛个度夏款能够,改变该对象尺寸后生成新的UIImage对象。这种情况下,就会产生搭理那个的autorelease对象。

        for (int i = 0; i < 图像数; i++) {
            
            // 读入图像
            
            // 大量产生autorelease对象
            
            // 由于没有废弃autoreleasePool对象最终导致内存不足
            
        }
    

    在这种情况下,有必要在适当的地方生成持有或者废弃

    for (int i = 0; i < 图像数; i++) {
            
            NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
            
            // 读入图像, 大量产生autorelease对象
            
            [pool drain];
            
            // 通过[pool drain]
            // autorelease的对象被一起release
            
        }
    

    更直观一点,我们现在用用字符串拼接,并且观察内存变化,代码如下:
    memory会持续增长,可想而知,如果我们将来NSString替换成占用内存更大的UIImage,会导致内存的增长更快和更大,严重的时候还会导致App崩溃。

        int largeNum = 999 * 9999;
        for (int i = 0; i < largeNum; i++) {
            
            NSString *str = @"Hellow World";
            str = [str stringByAppendingFormat:@" - %d", i];
            str = [str uppercaseString];
            NSLog(@"%@", str);
            
        }
    

    修改如下

         for (int i = 0; i < largeNum; i++) {
            
            @autoreleasepool {
                
                NSString *str = @"Hellow World";
                str = [str stringByAppendingFormat:@" - %d", i];
                str = [str uppercaseString];
                NSLog(@"%@", str);
    
            }
            
        }
    

    autoreleasePool实现原理

    GNUSetup和Cocoa框架实现的大体思路总结
    如果调用NSObject类的autorelease方法,该对象就会被追加到正在使用的NSAutoReleasePool对象中的数组里。这个数组维护处在autoreleasepool的所有类对象,当一个对象进入autoreleasepool的时候就像数组添加该类,如果是多层autoreleasepool嵌套,理所当然的会使用最内侧的对象。代码如下:

    - (void)drain
    {
        [self dealloc];
    }
    
    - (void)dealloc
    {
        [self emptyPool];
        [array release];
    }
    
    - (void)emptyPool{
        
        for (id obj in array) {
            [obj release];
        }
        
    }
    

    1.3 ARC规则

    概要

    ARC / MRC可以混编

    内存管理的思考

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

    以上这些规则同样适用,只是源代码的记述方法上稍微有些不同。

    所有权修饰符

    • __ strong修饰符
    • __weak修饰符
    • __unsafe_unretain修饰符
    • __autoreleasing修饰符
    __ strong修饰符

    __ strong修饰符是id类型和对象类型默认的修饰符。以下两句代码等价。

    id obj = [[NSObject alloc] init];
    
    id __strong obj = [[NSObject alloc] init];
    

    __ strong修饰符表示对对象的强引用,持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用对象会被释放掉,以下两句代码等价(注意区分MRC / ARC)。

    {
      id __strong obj = [[NSObject alloc] init];
    }
    
    // 在ARC无效时
    {
      id obj = [[NSObject alloc] init];
      [obj release];
    }
    

    附有_strong修饰符之间的变量可以相互赋值,我们用代码来掩饰这一过程

    {
        
        id __strong obj0 = [[NSObject alloc] init]; /* 对象A */
        
        // obj0对对象A强引用
        
        id __strong obj1 = [[NSObject alloc] init]; /* 对象B */
        
        // obj1对对象B强引用
        
        id __strong obj2 = nil;
    
        // obj2不持有任何对象
        
        obj0 = obj1;
        
        /*
         obj0 持有由obj1赋值对象B的抢引用
         因为obj0被赋值,所以原先持有的对象A的强引用失效。对象A的所有者不存在,因此废弃对象A
         此时,对象B的持有者变成了 obj0和obj1
         */
        
        obj2 = obj0;
        
        /*
         此时,对象B的持有者变成了 obj0 obj1 obj2
         */
        
        obj1 = nil;
        
        /*
         因为nil被赋予到obj1,所以对x对象B的强引用失效
         此时,对象B的持有者变成了 obj0 obj2
         */
        
        obj0 = nil;
        
        /*
         因为nil被赋予到obj0,所以对x对象B的强引用失效
         此时,对象B的持有者变成了 obj2
         */
        
        obj2 = nil;
        
        /*
         因为nil被赋予到obj0,所以对x对象B的强引用失效
         对象B的所有者不存在,因此废弃对象B
         */
        
    }
    

    __strong修饰符和后面要讲的__weak和__autoreleasing修饰符一起,可以保证这些附有修饰符的自动变量初始化为nil
    下面两端代码等价

        id __strong obj0;
        id __weak obj1;
        id __autoreleasing obj2;
    
        id __strong obj0 = nil;
        id __weak obj1 = nil;
        id __autoreleasing obj2 = nil;
    
    __ weak修饰符

    到此位置,strong似乎可以完美的管理内存,但是还有一个重大的问题就是循环引用
    以下为循环引用示例代码
    类成员变量相互引用

    {
        
        id test0 = [[Test alloc] init]; /* 对象A */
        
        /*
         test0持有Test对象A的强引用
         */
        
        id test1 = [[Test alloc] init]; /* 对象B */
        
        /*
         test1持有Test对象B的强引用
         */
        
        [test0 setObject: test1];
        
        /*
         Test对象A的obj_成员变量持有Test对象B的强引用
         
         此时拥有Test对象B的强引用的变量为 Test对象A的obj_和test1
         */
        
        [test1 setObject: test0];
       
        /*
         Test对象B的obj_成员变量持有Test对象B的强引用
         
         此时拥有Test对象A的强引用的变量为 Test对象A的obj_和test0
         */
    }
    
    /*
     
     因为Test0变量超出其作用域,强引用失效
     所以自动释放Test对象A
     
     因为Test1变量超出其作用域,强引用失效
     所以自动释放Test对象B
     
     此时
     持有A的强引用变量为B的ocbj_
     持有B的强引用变量为A的ocbj_
     
     发生内存泄漏!
     
     */
    

    对自身的循环引用

        id test0 = [[Test alloc] init]; /* 对象A */
        [test0 setObject:test0];
    

    __weak修饰符提供弱引用。弱引用不能持有对象实例。
    错误写法,这么写,因为生成的实例没有被引用所以生成之后会立即被销毁

       id __weak obj = [[NSObject alloc] init];
    
        id __strong obj = [[NSObject alloc] init];
        id __weak obj1 = obj;
    
    {
        // 自己生成并持有对象
        id __strong obj0 = [[NSObject alloc] init];
        
        // 因为obj0的对象为强引用,所以自己持有对象
        
        id __weak obj1 = obj0;
        
        // obj1变量持有生成对象的弱引用
        
    }
    
    /*
     
     因为obj0变量超出其作用域,强引用失效
     所以自动释放自己持有的对象
     因为对象的所有者不存在,所以废弃该对象
     
     */
    

    这样的话,两个类互相弱引用也不会有问题。
    此外__weak还有一个优点,在持有某对象的弱引用时,若该对象被废弃,则弱引用将自动失效且处于nil的赋值状态。

    __ unsafe_unretained修饰符

    unsafe_unretained修饰符正如其名unsafe,是不安全的修饰符,尽管ARC的内存管理是编译器的工作,但附有__ unsafe_unretained修饰符的变量不属于编译器内存管理对象。

        /* ARC无效 */
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        
        id obj = [[NSObject alloc] init];
        
        [obj autorelease];
        
        [pool drain];
    
    @autoreleasepool {
            id __autoreleasing obj = [[NSObject alloc] init];
        }
    

    可以非显示的使用__autoreleasing修饰。
    以下两句等价

    id obj;
    
    id __strong obj1;
    

    以下两句等价

    id *obj;
    
    id __autoreleasing *obj1;
    

    以下两句等价

    NSobject **obj;
    
    NSobject *__autoreleasing *obj;
    

    相关文章

      网友评论

          本文标题:内存管理

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