美文网首页
iOS内存管理

iOS内存管理

作者: 金约21依代 | 来源:发表于2017-12-15 01:06 被阅读0次

    MRC

    对象持有讨论

    先看一个例子:

    // 这个方法生成对象并返回
    - (id)object
    {
        // 通过 alloc/new/copy/mutableCopy 方法生成对象并自己持有
        id obj = [[NSObject alloc] init];
        
        /**
         autorelease使得自己不去持有这个对象
         自己持有对象的话,可以通过[obj release] 去释放对象,但当对象自身autorelease时,释放是由自身自动释放的,就不能通过release了(个人理解),这个时候去调用release会造成崩溃.
         */
        
        [obj autorelease];
        
        return obj;
    }
    
    // 方法调用:
    
    /** 
     这里通过方法 object获取到对象,这个对象是存在的,但是自己并不持有这个对象,对自己不持有的对象是不能去释放他的.
     这个对象为什么存在,上面不是自己加了autorelease吗?
     其实这就是autorelease和release的区别:调用release会立即释放,但autorelease不会,调用autorelease会将对象注册到autoreleasepool中,当pool结束时会自动调用release.这里其实存在着一个哨兵指针. autoreleasepool 的释放是 NSRunloop 决定的.具体可以参考杨潇玉的博客.
     */
    id obj1 = [obj0 object];
    
    // 如果让obj1区持有这个对象呢? 可以调用retain,他可以将调用autorelease方法取得的对象变为自己持有
    [obj1 retain];
    
    

    总结:释放非自己持有的对象会造成程序崩溃,因此绝不能释放非自己持有的对象

    关于内存释放的研究

    NSAutoreleasePool什么时候释放?NSAutoreleasePool的生命周期是怎样的?是在方法体调用结束就释放?其实不是.

    在大量产生autorelease对象时,只要不废弃NSAutoreleasePool对象,那么生成的对象就不能被释放,因此有时会产生内存不足的现象.典型例子是读入大量图像时,图像文件读入到NSData对象,从中生成UIImage对象.这种清空下会产生大量autorelease对象.这种情况就需要再适当的地方生成,持有,废弃NSAutoreleasePool对象.

    比如在MRC时可以这样写:

    for(int i = 0; i<100; i++) {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        // 读入图像,大量产生autorelease对象
        //  ........
        [pool drain];   // 这句代码可以让autorelease对象被一起release
    }
    

    Zone

    我们经常看到不少方法里有这个单词,比如NSZoneMalloc,NSDefaultMallocZone等等.这个单词是干什么的呢?

    NSZone其实是为了防止内存碎片化而引入的结构.它ui内存分配的区域本身进行多重化管理,根据对象目的,大小分配内存,提高内存管理效率.

    但是根据苹果官方所说,现在的运行时系统,内存管理的效率本身已经很高,使用区域来管理内存反而会引起内存使用效率低下及源代码复杂化.

    ARC

    首先看看ARC中的4个修饰符:

    • __strong (默认修饰符)
    • __weak
    • __unsafe_unretained
    • __autoreleasing

    在MRC中有个问题,通过alloc的方式得到的对象会被自己所持有,而通过比如NSArrayarray方法得到的对象不会被持有:

    **  MRC  **
    // obj持有对象
    id obj = [[NSObject alloc] init];
    
    // array不持有对象
    NSArray *arr = [NSMuableArray array];
    

    那在ARC中是怎么样的呢?

    **  ARC  **
    // obj持有对象
    id obj = [[NSObject alloc] init];
    // 相当于:
    id __strong obj = [[NSObject alloc] init]; // 由于obj为强引用,所以自己持有对象
    
    /**
     这里的array也是持有对象的.
     array类方法让arr取得 非自己生成并持有  的对象
     但因arr为强引用,所以持有对象
     在超出作用域(方法体)后,强引用失效,会自动释放自己所持有的对象
    */
    NSArray *arr = [NSMuableArray array];
    // 相当于
    NSArray *__strong arr = [NSarray array];
    

    赋值方法对对象的引用

    __strong
    id __strong objA = [[NSObject alloc] init];  // objA持有对象A的强引用
    id __strong objB = [[NSObject alloc] init];  // objB持有对象B的强引用
    id __strong objC = nil;  // objC不持有任何对象
    
    /**
     objA持有由objB赋值对象B的强引用
     因为objA被赋值,所以原先持有的对象A的强引用失效
     对象A的所有者不存在,因此废弃对象A
     
     此时,持有对象B的强引用的变量为
     objA 和 objB
    */
    objC = objA;
    
    /**
     objC 持有由objA赋值的对象B的强引用
     此时,持有对象B的强引用的变量为
     objA 和 objB 和 objC
    */
    objB = objC;
    
    /**
     nil被赋予了objB,所以objB对对象B的强引用失效
     
     此时,持有对象B的强引用的变量为
     objA 和 objC
     
     
     这里做一个理解,为什么objB = nil, objA还是和objC还是指向对象B呢? objA和objC 会变成nil吗?
     不会.objB = nil;这句话的意思是objB抛弃了原先的对象,重新指向了一个对象(nil),原先的对象B会通过retainCount - 1 的方式回应这次'抛弃'. 但如果B不是通过从新指向另一块内存的方式,而是改变自身,比如 [objB addObject],那么他是对自身对象B进行的操作,这个时候objA和objC也会相应被改变.
    */
    objB = nil;
    

    总结:

    • 自己生成的对象,自己所持有
    • 非自己生成的对象,自己也可以持有
    • 不再需要自己持有的对象时释放
    • 非自己持有的对象无法释放
    __weak

    __strong修饰符基本可以完美的进行内存管理了,但是遇到循环引用就需要__weak了.

    比如两个Person实例 personApersonB,都有一个id类型的obj属性, obj属性被分别赋值 personBpersonA,即:

    Person personA = [[Person alloc] init]; // A 对象
    Person personB = [[Person alloc] init]; // B 对象
    
    personA.obj = personB;
    personB.obj = personA;
    

    这个时候A对象的持有者是两个 personApersonBobj 属性;
    B对象的持有者也是两个 personBpersonAobj 属性;

    personA 变量超出其作用域,强引用失效,自动释放对象A, 当personB 变量超出其作用域,强引用失效,自动释放对象B. 此时,持有 A 对象的强引用变量为对象Bobj,持有B对象的强引用为对象A的obj. 发生内存泄漏.

    所谓内存泄漏,就是应当废弃的对象在超出其生命周期后继续存在.

    上面的例子,如果A 对象持有自身,即personA.obj = personA,这样也会造成循环引用.

    还有一个例子,这里做一下分析:

    id __weak objA = nil;
    {
        id __strong objB = [[NSObject alloc] init];
        objA = objB;
        
        NSLog(@"A = %@",objA);
    }
    
    NSLog(@"A = %@",objA);
    

    输出结果为:

    A = <NSObject: 0x45f140e>
    A = (null)
    

    对于id __weak objA = nil;这句代码,我之前也一直不理解,先不论objA是否=nil, __weak修饰的对象如果没有强持有者不会立即释放掉吗,为什么objA 还存在?

    其实,objA是指针,是在栈里的,objA指向的对象(比如对象A)是在堆内存里的,确实,对象A释放后objA也会随之销毁,但是由于objA是在栈里的,并且objA其实是autorelease的,该指针只会被标记为要释放,等待autorelease要释放(drain)时候,objA才会通过哨兵对象确定要被释放,从而发送release消息将它释放掉,所以在这个方法体里,objA还是存在的.所以,在使用附有__weak修饰符的变量时就必定要使用注册到autorelepool中的对象.其实,__weak申明的变量会自动将对象注册到autoreleasepool中.

    所以,第一次打印,是打印的objA通过弱引用持有的对象,这个对象在方法体结束后被释放了,所以第二次打印为null.

    <font size='3' color='0000ff'>weak自动置为nil的实现</font>

    id __weak obj1 = obj;
    

    这句话做了什么?

    1. 首先,obj1通过obj进行初始化,调用函数
    objc_initWeak(&obj1, obj)
    

    当释放的时候,第二个参数传递0,即

    objc_destroyWeak(&obj1, 0)
    
    1. objc_storeWeak函数把obj1的地址和被赋值对象通过键值对的方式注册到weak表里.当对象释放的时候,通过废弃对象的地址作为键值进行检索,就能获取到对应__weak变量的地址,然后将所有附有__weak修饰的变量地址全部赋值nil,之后再把她从weak表中移除掉就好了.
    2. 大量使用__weak会消耗相应的CPU资源(要注册weak表,并且一个键值,可以注册多个变量的地址),所以一般只在需要避免循环引用的时候使用__weak.
    __unsafe_unretained

    这个修饰符是不安全的.ARC的内存管理是编译器的工作,但附有__unsafe_unretained修饰符的变量不属于编译器内存管理的对象.

    同样用上面的例子做说明,第一行换成id __unsafe_unretained objA = nil;

    打印结果为:

    A = <NSObject: 0x45f140e>
    A = <NSObject: 0x45f140e>
    

    奇怪,第二次打印结果正常,这是怎么回事?

    当方法体出来后,objB强引用失效,自动释放自己持有的对象,这个时候没有强引用去持有这个对象,对象释放.所以objA变量表示的对象已经被废弃,变成悬垂指针,这是一个错误访问.所以这一次的打印只是碰巧正常运行而已.虽然访问了已经被废弃的对象,但是应用程序在个别运行状况下才会崩溃.

    __autoreleasing

    ARC下,autorelease是不能用的(这个修饰符是可以使用的),包括NSAutoreleasePool类也不能用,MRC下是这样使用的:

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

    drain方法废弃正在使用的NSautoreleasePool对象的过程为:

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

    ARC下,该源代码写成如下这样:

    @autoreleasepool {
        
        id __autoreleasing obj = [[NSObject alloc] init]; // ARC无效时会调用autorelease方法.另外,__autoreleasing修饰符一般是可以非显示地使用的
        
    }
    

    在使用alloc/new/copy/mutableCopy以外的方法来取得对象的时候,该对象会被注册到autorelpool.编译器会检查方法名是否以这几个单词开始,如果不是的则自动将返回的对象注册到autoreleasepoll.

    另外,init方法返回值的对象不会注册到autoreleasepool.

    不管是否使用了ARC,调试用的非公开函数_objc_autoreleasePoolPrint()都是可以使用的.不过有可能报错Implicit declaration of function - C99,这种情况可以通过Build Setting -> C Language Dialect修改为GNU99GNU89解决.

    使用ARC的时候,对象型变量补鞥呢作为C语言结构体(struct/union)的成员.因为ARC把内存管理的工作交给编译器,那么编译器必须知道并管理对象的生存周期.要把对象型变量加入到结构体成员中时,可以强制转换为void *或者附加__unsafe_unretained修饰符(因为__unsafe_unretained修饰符修饰的变量不属于编译器的内存对象).

    **  MRC  **
    id obj = [[NSObject alloc] init];
    void *p = obj;
    
    // 也可以反过来赋值
    id obj2 = p;
    [obj2 release];
    
    **  ARC  **
    // 要使用__bridge桥接转换
    id obj = [[NSObject alloc] init];
    void *p = (__bridge void *)obj;
    id obj2 = (__bridge id)p;
    

    相关文章

      网友评论

          本文标题:iOS内存管理

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