美文网首页
ARC的内存管理方式

ARC的内存管理方式

作者: foreverSun_122 | 来源:发表于2018-04-11 11:08 被阅读0次

    什么是ARC

    ARC(自动引用计数)是一个编译期技术,介于垃圾回收(GC)和MRC之间,ARC让程序员不再需要书写retain/release/autorelease语句,runtime会维护一张引用计数表,编译器会在编译期在合适的位置自动给用添加retain/release/autorelease,它的特点是自动引用技术简化了内存管理的难度

    ARC内存管理的思考方式:

    ARC内存管理的思考方式与MRC一致。

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

    ARC代码编写规则

    1. 不能在程序中定义和使用:retain、release、autorelease和retainCount
    2. 使用@autoreleasepool代替NSAutoreleasePool
    3. 不用在dealloc中释放实例变量(可以在dealloc中释放资源),也不需要调用[super dealloc]

    所有权修饰符

    id类型:用于隐藏对象类型的类名部分,类似于C语言中的void*;
    ARC有效时,id类型和对象类型必须加上所有权修饰符:

    • __strong修饰符
    • __weak修饰符
    • __unsafe_unretained修饰符
    • __autoreleasing修饰符

    __strong修饰符

    __strong修饰符是默认的修饰符

    //两种方式是等价的
    id obj = [[NSObject alloc]init];
    id __strong obj = [[NSObject alloc]init]
    
    

    特性:strong表示强引用,持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。

    1. 与自己生成并持有对象
    //ARC有效
    {
        //自己生成,自己持有,obj为强引用,持有变量
        id __strong obj = [[NSObject alloc]init];
        //todo
        .......
    }
    //超出作用域,强引用失效
    
    //以上代码在ARC无效下等价于
    {
        id obj = [[NSObject alloc]init];
        //todo
        ......
        [obj release];
    }
    

    2、取得非自己生成但持有的对象

    //取得非自己生成的对象,obj为强引用,所以持有该对象
    id __strong obj = [NSArray array];
    
    

    3、附有__strong修饰符的变量可以相互赋值

    //obj0持有对象A的强引用
    id __strong obj0 = [[NSObject alloc]init];
    
    //obj1持有对象B的强引用
    id __strong obj1 = [[NSObject alloc]init];
    
    //obj2不持有任何对象
    id __strong obj2 = nil;
    
    /*
     * obj0持有obj1赋值的对象B的强引用,对象B的持有者为obj0、obj1
     * obj0被赋值,原先持有的对象A的强引用失效,对象A的所有者不存在,因此对象A废弃
     */
    obj0 = obj1;
    
    /*
     * obj2持有obj0赋值的对象B的强引用
     * 对象B的持有者为:obj0、obj1、obj2
     */
    obj2 = obj0;
    
    

    4、附有__strong修饰符的变量作为方法的参数也能正确的管理其对象的所有者

    总结:通过__strong修饰符,不必再次键入retain或release,完美地满足了“引用计数式内存管理的思考方式”。

    __weak修饰符

    __weak修饰符,提供弱引用,弱引用不能持有实例对象,可以避免循环引用

    /*
     * 编译器会给警告
     * obj为弱引用,并不持有对象,生成的对象会被立即释放,所以会给警告
     */
    id __weak obj = [[NSObject alloc]init];
    
    //obj0强引用持有对象,obj1弱引用不持有对象
    id __strong obj0 = [[NSObject alloc]init];
    id __weak obj1 = obj0;
    

    注: 在持有某对象的弱引用时,若该对象被废弃,则弱引用自动失效,且处于nil被赋值的状态(空弱引用)

    __unsafe_unretained修饰符

    __unsafe_unretained是不安全的修饰符,修饰的变量不属于编译器的内存管理对象,同__weak修饰符一样,不能持有实例对象

    /*
     * 编译器警告,obj不持有NSObject对象
     */
    id __unsafe_unretained obj = [[NSObject alloc]init];
    
    id __unsafe_unretained obj1 = nil;
    {
        //obj0强引用NSObject对象
        id __strong obj0 = [[NSObject alloc]init];
        //obj1不持有NSObject对象
        obj1 = obj0;
        NSLog(@"A:%@",obj1);
    }
    /*
     * obj0变量超出作用域,强引用失效,释放持有变量
     * obj0指向的对象被废弃(悬垂指针),成为僵尸对象
     */
    NSLog(@"B:%@",obj1);
    
    执行结果:
    A: <NSObject: 0x753e180>
    B: <NSObject: 0x753e180>
    

    最后一行NSlog,碰巧正常运行。因为obj0指向的对象已经被废弃,不能被访问

    为什么要有__unsafe_unretained修饰符?

    在iOS4以及OS X Snow Leopard的应用程序中,必须使用__unsafe_retained修饰符来代替__weak__unsafe_unretained修饰的对象赋值给__strong修饰符的变量时,必须保证被赋值对象确实存在。

    __autoreleasing修饰符

    ARC无效时

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

    ARC有效时,代码写为

    //ARC指定“@autoreleasepool块”来替代“NSAutoReleasePool类对象生成、持有以及废弃”这一范围
    @autoreleasepool {
        //添加__autoreleasing修饰符来代替调用autorelease方法
        id __autoreleasing obj = [[NSObject alloc]init];
    }
    

    非显示使用__autorelease的情况

    __autoreleasing__strong一样通常都不会显示添加。

    • ARC有效时,在取得非自己生成并持有的对象时,编译器会检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是则自动将返回的对象值注册到autoreleasepool中。
    @autoreleasepool {
        // 变量为强引用,所以自己持有对象
        //但编译器判断其方法名后,自动将其注册到autoreleasepool
        id __strong obj = [NSMutableArray array];
    }
    
    • alloc/new/copy/mutableCopy开头的非自己生成但持有的方法中定义的对象
    + (id)array
    {
        //obj生成并持有对象,强引用
        id obj = [[NSMutableArray alloc]init];
        //强引用在obj超出作用域后会释放对象,但是该对象又作为返回值不能被释放,所以编译器会自动注册到autoreleasepool中
        return obj;
    }
    
    //得到的是注册到autoreleasepool中的对象
    id obj = [NSMutableArray array];
    
    • 访问__weak修饰的对象,实际上访问的是注册到autoreleasepool中的对象
    id __weak obj1 = obj0;
    NSLog(@"class = %@", [obj1 class])
    
    //等价于
    
    id __weak obj1 = obj0;
    id __autoreleasing tmp = obj1;
    NSLog(@"class = %@", [tmp class]);
    
    

    __weak修饰的对象为弱引用,在访问时可能会随时被废弃,只要把对象注册到autoreleasepool中,就能在@autoreleasepool块结束前确保对象存在

    • id的指针或者NSObject的指针没有显示指定时,会被默认被附上__autoreleasing修饰符
    //id的指针
    id *obj;
    id __autoreleasing *obj;
    
    //对象的指针
    NSError **error;
    NSError * __autoreleasing *error;
    
    - (BOOL)performOperationWithError:(NSError **)error;
    
    //error为对象指针,所以默认修饰符是__autoreleasing;
    - (BOOL)performOperationWithError:(NSError *__autoreleasing *)error {
        *error = [[NSError alloc] initwithDomain:MyApplication code:errorCode userInfo:nil];
        return NO
    
    }
    

    这里- (BOOL)performOperationWithError:(NSError **)error;与除alloc/new/copy/mutableCopy外的其他非自己生成但持有的方法一样,{}里[[NSError alloc] initwithDomain:MyApplication code:errorCode userInfo:nil]生成的对象因为超出作用域后会释放对象,所以编译器会将其放入到autoreleasepool中,所以这里的alloc方法生成的对象(理论上是__strong修饰的对象)能够正常赋值给__autoreleasing修饰的*error

    注:赋值给对象指针时,所有权修饰符必须一致

    //默认是__strong
    NSError *error = nil;
    //默认是__autoreleasing
    NSError **pError = &error;  //报错
    
    NSError * __autoreleasing *pError1 = &error;    //不报错
    
    //不报错
    NSError _strong *error = nil;
    BOOL result = [obj performOperationWithError:&error];
    

    上面的error是__strong修饰的,但是performOperationWithError:里的参数是__autoreleasing修饰的,但是没有因修饰符不一致而报错,原因是编译器做了如下转换:

    NSError __strong *error = nil;
    NSError __autoreleasing *tmp = error;
    BOOL result = [obj performOperationWithError:&tmp];
    

    注:显示指定__autoreleasing时要注意,对象变了要为自动变量(包括局部变量、函数以及方法参数)

    总结:

    • __strong__weak以及__autoreleasing修饰的变量初始值默认为nil
    • 自己生成并持有的对象,即以alloc/new/copy/mutableCopy开头的方法生成的对象,编译器默认给变量添加修饰符__strong
    • 非自己生成但持有的对象,编译器默认给变量添加修饰符__autoreleasing
    • 非自己生成但持有对象的方法中返回的值默认是__autoreleasing
    • id指针和对象指针默认是__autoreleasing
    • __weak用于避免循环引用,在对象废弃时,自动降变量置为nil
    • __unsafe_unretained在iOS4中用来替代__weak,但是在对象被废弃时,不会将变量置为nil,因此在将__unsafe_unretained修饰的变量赋值给__strong修饰的变量时要确保对象是否存在
    • 修饰符是ARC时才用的,MRC没有修饰符
    • 修饰符用于实例变量、自动变量等变量的声明,不能用于属性

    参考资料:

    • Objective-C高级编程iOS与OS X多线程和内存管理

    相关文章

      网友评论

          本文标题:ARC的内存管理方式

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