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

2017-10-09  本文已影响131人  朽木自雕也

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

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

这里主讲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一致,用来修饰基础数据类型

上一篇下一篇

猜你喜欢

热点阅读