美文网首页
iOS 内存管理(二)ARC属性修饰符

iOS 内存管理(二)ARC属性修饰符

作者: 朽木自雕也 | 来源:发表于2017-10-09 10:45 被阅读131次

    ARC在编译期间,根据Objective-C对象的存活周期,在适当的位置添加retain和release代码。从概念上讲,ARC与手动引用计数内存管理遵循同样的内存管理规则,但是ARC也无法防止循环强引用。

    ARC模式下的修饰符来修饰变量和声明属性:

    • 声明变量的修饰符:__strong, __weak, __unsafe_unretained, __autoreleasing
    • 声明属性的修饰符:strong, weak, unsafe_unretained,assign
    • 对象和CoreFoundation对象直接的转换修饰符号:__bridge,__bridge_retained或CFBridgingRetain, __bridge_transfer或CFBridgingRelease。
    • 对于线程的安全,有nonatomic,这样效率就更高了,但是不是线程的。如果要线程安全,可以使用atomic,这样在访问是就会有线程锁。

    这里主讲iOS ARC所以就只讲weak strong assign这三个修饰符,__weak __strong __assign跟前面的一样

    __strong 修饰符

    先来看一段代码:

    NSObject *obj1 = [[NSObject alloc]init];
    id obj2 = obj1;
    

    __strong 修饰符是id类型和对象类型默认的所有权修饰符,也就是说上面的变量,实际上附加了所有权修饰符,id和对象类型在没有明确的指定所有权修饰符时,默认为__strong修饰符,上面代码实质上跟下面的代码一致

    __strong NSObject *obj1 = [[NSObject alloc]init];
    __strong id obj2 = obj1;
    

    通过__strong修饰符,不必再次键入retain或者release,完美地满足了“引用计数内存管理的思考方式”:</p>
    自己生成的对象,自己持有
    非自己生成的对象,自己也能持有
    不再需要自己持有的对象时释放
    非自己持有的对象无法释放

    事列代码

    NSObject *obj1 = [[NSObject alloc]init];
    NSObject *obj2 = obj1;
    NSLog(@"obj1:%@,obj2=%@",obj1,obj2);
    obj1 = nil;
    NSLog(@"obj1:%@,obj2=%@",obj1,obj2);
    

    输出为:<p>
    obj1:<NSObject: 0x100203ab0>,obj2=<NSObject: 0x100203ab0></p>
    obj1:(null),obj2=<NSObject: 0x100203ab0>

    __weak修饰符

    看起来好像通过 __strong 修饰符编译器就能完美地进行内存管理,大师遗憾的是,仅通过__strong修饰符时不能解决有些重大问题的。
    这里提到的重大问题就是引用计数试内存管理中必然会发生的“循环引用”的问题

    image

    例如前面出现的带有__strong修饰符的成员变量在持有对象时,很容易发生循环引用

    @interface Test : NSobject {
    
        id __strong _obj;
    }
    - (void)setObj:(id)obj;
    @end
    @implementation Test
    - (void)setObj:(id)obj {
        _obj = obj;
    }
    @end
    以下就为循环引用
    int main() {
        id test1 = [[Test alloc]init];/*对象a*/
        //test1持有Test对象a的强引用
        id test2 = [[Test alloc]init];/*对象b*/
        //test2持有Test对象b的强引用
        [test1 setObj:test2];
        /*
        test1对象a的obj成员变量持有test2对象b的强引用
        此时,持有test对象b的强引用的变量为Test对象a的_obj和test2
        */
        [test2 setObj:test1];
        /*
        test2对象b的obj成员变量持有test2对象a的强引用
        此时,持有test对象b的强引用的变量为Test对象b的_obj和test1
        */
        /*
        因为test1变量超出其作用域,强引用失效,所以自动释放对象a
        因为test2变量超出其作用域,强引用失效,所以自动释放对象b
        此时,持有test对象a的强引用的变量为test2对象b的_obj
        此时,持有test对象b的强引用的变量为test1对象a的_obj
        内存泄漏出现了,两个对象超出作用域都没有被释放
        */
        return 0;
    }
    下面这种情况,虽然只有一个对象,但在该对象持有其自身是也发生了循环引用
    int main(){
        id test = [[Test alloc]init];
        [test setObj:test];
        //对自身的强引用,自引用
        return 0;
    }
    
    image
    __weak id obj = [[NSObject alloc]init];
    //实际上这样的代码是会报警告
    //此代码将自己生成的并吃藕的对象赋值给附有 __weak修饰符的变量 obj。即变量obj持有对持有对象的弱引用,因此,为了不以自己持有的状态来保存自己生成并持有的对象,生成的对象会立即被释放。编译器对此会给出警告。如果想下面这样,将对象赋值给一个strong修饰符的变量后再赋值给附有weak修饰符的变量,就不会发生警告了。
    {
        id __strong obj0 = [[NSObject alloc]init];
        id __weak obj1 = obj0;
    }
    

    因为带__weak修饰符的变量不持有对象,所以超出其变量作用域是,对象即呗释放。如果像下面这样将先前可能发生循环引用的类成员变量改成附有__weak修饰符的成员变量的话,该现象则可以避免

    image
    @interface Test : NSObject 
    {
     id __weak _obj;    
    }
    @end
    

    __weak修饰符还有另一有点。在持有某对象的弱引用时,若该对象被废弃,则此引用将自动失效且处于nil被赋值状态(空弱引用)。即下代码

    NSObject *obj1 = [[NSObject alloc]init];
    __weak NSObject *obj2 = obj1;
    NSLog(@"obj1:%@,obj2=%@",obj1,obj2);
    obj1 = nil;
    NSLog(@"obj1:%@,obj2=%@",obj1,obj2);
    

    输出结果为
    obj1:<NSObject: 0x100202e50>,obj2=<NSObject: 0x100202e50>
    obj1:(null),obj2=(null)
    使用__weak修饰符可避免循环引用,通过检查附有__weak修饰符变量
    __weak修饰符智能用于iOS5以上及OS XLion以上版本的应用程序,iOS4以及OS X Snow Leopard 的应用程序中可以使用__unsafe_unretained来代替。

    __unsafe_unretained 修饰符

    __unsafe_unretained修饰符正如其名unsafe所示,不安全的所有权修饰符。尽管arc模式的内存管理是编译器的工作,但附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象。这一点在使用时一定要注意<p>

    id __unsafe_unretained obj = [[NSObject alloc] init];<p>
    

    <html>
    该代码将自己生成并持有的对象赋值给附有__unsafe_unretained修饰符的变量中,虽然使用了unsafe的变量,但是编译器并不会忽略,而是给出适当的警告。<p>
    </html>

    warning:Assigning retained object to unsafe_unretained variable; object will be released after assignment
    

    附有__unsafe_unretained修饰符的变量同附有__weak修饰发的变量一样,因为自己生成并持有的对象不能继续为自己所有,所以生成的对象会立即被释放。到这里,__unsafe_unretained修饰符和__weak修饰符时一样的,下面我们来看一看差异。

    __unsafe_unretained id obj1 = nil;
    ({
            id obj2 = [[NSObject alloc] init];
            obj1 = obj2;
            NSLog(@"obj1:%@,obj2:%@",obj1,obj2);
    });
    NSLog(@"obj1:%@",obj1);
    

    代码执行结果为

    obj1:<NSObject: 0x1003007c0>,obj2:<NSObject: 0x1003007c0>
    obj1:<NSObject: 0x1003007c0>
    

    <html>
    发生了什么,来看一看
    </html>

             __unsafe_unretained id obj1 = nil;
            ({
                /** 自己生成并持有对象*/
                id obj2 = [[NSObject alloc] init];
                /** 
                 *因为obj2变量为强引用,所以自己持有对象
                 */
                obj1 = obj2;
                /**虽然obj2变量赋值给了obj1,但是obj1变量即不持有对象的强引用也不持有弱引用
                 */
                NSLog(@"obj1:%@,obj2:%@",obj1,obj2);
            });
            /** 因为obj2变量超出其作用域,所以自动释放自己持有的对象
             * 此时对象无持有者,所以废弃该对象
             */
            NSLog(@"obj1:%@",obj1);
            /** 
             *输出obj1比那两表示的对象
             *obj1变量表示的对象已经被废弃(悬垂指针)!错误访问!
             */
    

    也就是说,最后一行的NSLog只是碰巧正常运行而已。虽然访问了已经被废弃的对象,但是应用程序在个别运行状态才会崩溃。
    在使用__unsafe_unretained修饰符时,赋值给附有__strong修饰符的变量时有必要确保被赋值的对象确实存在。
    但是,在使用前,让我们在一尺想想为什么需要使用附有__unsafe_unretained修饰符变量。
    比如在iOS4以及OS X Snow Leopard的应用程序中,必须使用__unsafe_unretained修饰符来替代__weak修饰符。赋值给附有__unsafe_unretained修饰符变量的对象在通过该变量使用时,如果没有确保其实际存在,那么应用程序就会崩溃。

    __autoreleasing修饰符

    ARC有效是autorelease会如何呢,不能使用autorelease方法。另外,也不能使用NSAutoreleasePool类。这样一来,虽然autorelease无法直接使用,但实际上,ARC有效时autorelease功能起作用的

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

    指定“@autoreleasepool块”来替代“NSAutoreleasePool类对象生成、持有以及废弃”这一范围。
    另外,ARC有效是,要通过将对象赋值给附加了__autoreleasing修饰符的变量来替代调用autorelease方法。对象赋值给附有__autoreleasing对象修饰符的变量等价于ARC五小时调用对象那个的autorelease方法,即对象被注册到autoreleasepool。
    也就是说可以理解为,在ARC有效时,用@autoreleasepool块来替代NSAreleasePool类,用附有__autoreleasing修饰符的变量替代autorelease方法。

    image

    assign属性修饰符跟__unsafe_unretained一致,用来修饰基础数据类型

    相关文章

      网友评论

          本文标题:iOS 内存管理(二)ARC属性修饰符

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