Objective-C 内存管理之ARC规则

基本概念

ARC为自动引用计数,引用计数式内存管理的本质并无改变,ARC只是自动处理“引用计数”的相关部分。安全

在编译上,能够设置ARC有效或无效。默认工程中ARC有效,若设置无效,则指定编译器属性为-fno-objc-arcbash


内存管理思考方式

同引用计数相同框架

  • 本身生成的对象,本身持有
  • 非本身生成的对象,本身也能持有
  • 本身持有的对象不须要时释放
  • 非本身持有的对象没法释放

全部权修饰符

  • __strong
  • __weak
  • __unsafe_unretained
  • __autoreleasing

__stong修饰符

__strong修饰符是id类型和对象类型默认的全部权修饰符。 也就是说,如下代码中的id变量实际上都被家了全部权修饰符
函数

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

在ARC无效时,写法虽然同ARC有效时一致,但spa

{
    id __strong obj = [[NSObject alloc] init];
}
复制代码

以上代码明确指定了变量的做用域。代理

ARC无效时,该源码可记述以下:指针

/** ARC无效 */
{
    id obj =[[NSObject alloc] init];
    [obj release];
}
复制代码

为了释放生成并持有的对象,增长调用了release方法的代码。该源代码进行的动做跟ARC有效时的动做彻底同样。code

如上所示,附有__strong修饰符的变量obj在超出其变量做用域是,即在该变量被废弃是,会释放其被赋予的对象。对象

__strong修饰符表示对对象的“强引用”。持有强你用的变量在超出其做用域时被废弃,虽则会强引用的失效,引用的对象会随之释放。

/**本身生成并持有对象*/
{
    id __strong obj = [[NSObject alloc] init];
    /**由于变量obj为强引用,因此本身持有对象*/
}
/*
 *由于变量obj超出其做用域,强引用失效,因此自动释放本身持有的对象。
 *对象的全部者不存在,由于废弃该对象。
 */
复制代码

取得非本身生成并持有的对象时,代码以下:

/**取得非本身生成并持有的对象*/
{
    id __strong obj = [NSMutableArray array];
    /**由于变量obj为强引用,因此本身持有对象*/
}
/**由于变量obj超出其做用范围,强引用失效,因此自动地释放本身持有的对象*/
复制代码

变量的赋值以下:

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;
/*
 *obj2持有由obj0赋值的对象B的强引用
 *此时,持有对象B的强引用的变量为obj0,obj1和obj2;
 */
 
obj1 = nil;
/*
 *由于nil被赋予了obj1,因此对对象B的强引用失效。
 *此时,持有对象B的强引用的变量为obj0和obj2;
 */
 
obj0 = nil;
/*
 *由于nil被赋予了obj1,因此对对象B的强引用失效。
 *此时,持有对象B的强引用的变量为obj0和obj2;
 */
 
obj2 = nil;
/*
 *由于nil被赋予了obj2,因此对对象B的强引用失效。
 *由于对象B的全部者不存在,因此废弃对象B.
 */

复制代码

__strong修饰符的变量,不只只在变量做用域中,在赋值上也可以正确得管理其对象的全部者。一样,在方法的参数上,也可使用附有__strong修饰符的变量。

@interface Test : NSObjecgt
{
    id __strong obj_;
}
- (void) setObject:(id __strong)obj;
@end

@implementation Test
- (id) init
{
    self = [super init];
    return self;
}

- (void) setObject:(id __strong)obj
{
    obj_ = obj;
}
@end
复制代码

使用该类以下:

{
    id __strong test = [[Test alloc] init];
    /*
     *test 持有Test对象的强引用
     */
    [test setObject:[[NSObject alloc] init];
    /*
     *Test对象的obj_成员,
     *持有NSObject对象的引用;
     */
}
/*
 *由于test变量超出其做用域,强引用失效,因此自动释放Test对象。
 *Test对象的全部者不存在,所以废弃该对象。
 *废弃Test对象的同时,Test的成员变量obj_也被废弃,
 *NSObject对象的强引用失效,自动释放NSObject对象。
 *NSObject对象的全部者不存在,所以废弃该对象。
 */

    
复制代码

另外,__strong修饰符同__weak __autoreleasing,能够保证将附有这些修饰符的自动变量初始化为nil。

经过__strong修饰符,没必要再次retain或者release,就能够知足引用计数内存管理的思考方式。

  • 本身生成的对象本身持有
  • 非本身生成的对象,本身也能够持有
  • 再也不须要本身持有的对象时释放
  • 非本身持有的对象没法释放

__weak修饰符

仅仅经过__strong修饰符不可以解决引用计数内存管理中的“循环引用”问题。

例如:

@interface Test : NSObjecgt
{
    id __strong obj_;
}
- (void) setObject:(id __strong)obj;
@end

@implementation Test
- (id) init
{
    self = [super init];
    return self;
}

- (void) setObject:(id __strong)obj
{
    obj_ = obj;
}
@end
复制代码

如下为循环引用:

{
    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对象A的强引用
     * 此时,持有Test对象A的强引用的变量为Test对象B的obj_和test0
     */
     
}
/*
 * 由于test0变量超出其做用域,强引用失效,因此自动释放Test对象A
 * 由于test1变量超出其做用域,强引用失效,因此自动释放Test对象B
 * 此时持有Test对象A的强引用的变量为Test对象B的obj_
 * 此时持有Test对象B的强引用的变量为Test对象A的obj_
 * 发生内存泄漏
 */

复制代码

循环引用容易发生内存泄漏。内存泄漏就是应当废弃的对象在超出其生存周期后继续存在。

如下代码也会引发内存泄漏(对自身的强引用)

id test = [[Test alloc] init];
[test setObject:test];

复制代码

使用__weak修饰符能够避免循环引用。

__weak修饰符与__strong修饰符相反,提供弱引用。弱引用不能持有对象实例。

id __weak obj = [[NSObject alloc] init];
复制代码

若是运行以上代码,编译器会发出警告。

Assigning retained object to weak variable; object will be released after assignment
复制代码

以上代码将本身生成并持有的对象赋值给附有__weak修饰符的变量obj。即变量obj持有对持有对象的弱引用。所以,为了避免以本身持有的状态来保存本身生成并持有的对象,生成的对象会被当即释放。若是将对象赋值给附有__strong修饰符的变量以后再赋值给附有__weak修饰符的变量,就不会发生警告了。

{
    /**本身生成并持有对象*/
    id __strong obj0 = [[NSObject alloc] init];
    /** 由于obj0变量为强引用,因此本身持有对象 */
    
    id __weak obj1 = obj0;
    /** obj1变量持有生成对象的弱引用 */
}
/*
 *由于obj0变量超出其做用域,强引用失效,因此自动释放本身持有的对象。
 *由于对象的全部者不存在,因此废弃该对象。
 */
复制代码

由于带__weak修饰符的变量(弱引用)不持有对象,因此在超出其变量做用域时,对象即被释放。以下代码便可避免循环引用。

@interface Test : NSObject
{
    id __weak obj_;
}
- (void) setObject:(id __strong) obj;
@end
复制代码

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

id __weak obj1 = nil;
{
    /**本身生成并持有对象*/
    
    id __strong obj0 = [[NSObject alloc] init];
    
    /**由于obj0变量为强引用,因此本身持有对象*/
    
    obj1 = obj0;
    
    /**obj1持有对象的弱引用*/
    
    NSLog(@"A: %@",obj1);
    /** 输出obj1变量持有的弱引用的对象*/
}
/*
 *由于obj0变量超出其做用域,强引用失效,因此自动释放本身持有的对象。
 *由于对象无持有者,因此废弃该对象。
 *废弃对象的同时,持有该对象弱引用的obj1变量的弱引用失效,nil赋值给obj1.
 */

NSLog(@"B: %@",obj1);
/** 输出赋值给obj1变量中的nil */

复制代码

该代码的运行结果为:

2017-12-05 20:13:28.458858+0800 ImageOrientation[6316:1604800] A: <NSObject: 0x604000207710>
2017-12-05 20:13:30.112086+0800 ImageOrientation[6316:1604800] B: (null)
复制代码

以上,使用__weak修饰符能够避免循环引用。经过检查__weak修饰符的变量是否为nil,能够判断被赋值的对象是否以废弃。

__unsafe_unretained 修饰符

__unsafe_unretained修饰符的变量部署与编译器的内存管理对象。(ARC式的内存管理式编译器的工做)

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

若是运行以上代码,编译器会发出警告。虽然使用了unsafe的变量,可是编译器并不会忽略。

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

附有__unsafe_unretained修饰符的变量同附有__weak修饰符的变量同样,由于本身生成并持有的对象不能继续为本身全部,因此生成的对象会当即释放。

id __unsafe_unretained obj1 = nil;
{
    /**本身生成并持有对象*/
    
    id __strong obj0 = [[NSObject alloc] init];
    
    /**由于obj0变量为强引用,因此本身持有对象*/
    
    obj1 = obj0;
    
    /**虽然obj0变量赋值给obj1,但obj1变量既不持有队形的强引用,也不持有对象的弱引用*/
    
    NSLog(@"A: %@",obj1);
    /** 输出obj1变量表示的对象*/
}
/*
 *由于obj0变量超出其做用域,强引用失效,因此自动释放本身持有的对象。
 *由于对象无持有者,因此废弃该对象。
 */

NSLog(@"B: %@",obj1);
/*
 * 输出obj1变量表示的对象
 *
 * obj1变量表示的对象已经被废弃(悬垂指针)
 * 指向曾经存在的对象,但该对象已经再也不存在了,此类指针称为悬垂指针。结果未定义,每每致使程序错误,并且难以检测。
 * 错误访问
 */

复制代码

该代码的运行结果为:

2017-12-06 08:45:54.005966+0800 ImageOrientation[6859:1736666] A: <NSObject: 0x604000011f00>

运行到NSLog(@"B: %@",obj1)时crash:Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)

复制代码

__autoreleasing 修饰符

ARC有效时,不能使用autorelease方法,也不能使用NSAutoreleasePool类。虽然autorelease没法直接使用,可是autorelease功能是起做用的。

ARC无效时代码以下:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autoreleae];
[pool drain];
复制代码

ARC有效时,代码能够写成下面这样:

@autoreleaepool{
    id __autoreleasing obj = [[NSObject alloc] init];
}
复制代码

指定“@autoreleasepool块”来代替“NSAutoreleasePool类对象生成,持有以及废弃”这一范围。

另外,ARC有效时,要经过将对象赋值给附加了 __autoreleaing修饰符的变量来代替调用autorelease方法。对象赋值给附有__autoreleasing修饰符的变量等价于在ARC无效时调用对象的autorelease方法,即对象被注册到autoreleasepool。

也就是说能够理解为,在ARC有效时,用@autoreleasepool块替代NSAutoreleasePool类,用附有__autoreleasing修饰符的变量替代autorelease方法。

通常来讲,不会显示地附加__autoreleasing修饰符。在取得非本身生成并持有的对象时,索然可使用alloc/new/copy/mutableCopy之外的方法来取得对象,但该对象已经被注册到了autoreleasepool。这是因为编译器会检查方法名是否以alloc/new/copy/mutableCopy开始,若是不是则自动将返回值的对象注册到autoreleasepool。(init方法返回值的对象不注册到autoreleasepool)

@autoreleasepool{
    /** 取得非本身生成并持有的对象*/
    id __strong obj = [MutableArray array];
    /*
     * 由于变量obj为强引用,因此本身持有对象。
     * 而且该对象由编译器判断其方法名后自动注册到autoreleasepool
     */
}
/*
 * 由于变量obj超出其做用域,强引用失效,因此自动释放本身持有的对象。
 * 同时,随着@autoreleasepool块的结束,注册到autoreleasepool中的全部对象被自动释放。
 * 由于对象的全部者不存在,因此废弃对象。
 */
复制代码

访问附有__weak修饰符的变量时,必须访问注册到autoreleasepool的对象。

id __weak obj1 = obj0;
NSLog(@"class = %@",[obj1 class]);
复制代码

等同于

id __weak obj1 = obj0;
id __autoreleasing temp = obj1;
NSLog(@"class = %@",[temp class]);
复制代码

由于__weak修饰符只持有对象的弱引用,而在访问引用对象的过程当中,该对象有可能被废弃。若是要把访问的对象注册到autoreleasepool中,那么在@autoreleasepool块结束以前都能确保该对象存爱。所以,在使用附有__weak修饰符的变量时就一定要shying注册到autoreleasepool中的对象。

一样地,id的指针或者对象的指针在没有显示指定时会被附加上__autoreleasing修饰符。


ARC 规则

在ARC有效的状况下编译源代码,必须遵照如下规则:

  • 不能使用retain/release/retainCount/autorelease
  • 不能使用NSAllocateObject/NSDeallocateObject
  • 必须遵照内存管理的方法命名规则
  • 不要显示调用dealloc
  • 使用@autoreleasepool块代替NSAutoreleasePool
  • 不能使用区域(NSZone)
  • 对象型变量不能做为C语言结构体(struct/union)的成员
  • 显示转换"id"和"void"

不能使用retain/release/retainCount/autorelease

内存管理是编译器的工做,由于没有必要使用内存管理的方法(retain/release/retainCount/autorelease)。只能在ARC无效且手动进行内存管理时才能使用。

不能使用NSAllocateObject/NSDeallocateObject

通常经过调用NSObject类的alloc类方法来生成并持有OC对象。alloc类方法其实是经过直接调用NSAllocateObject函数来生成并持有对象的。

须遵照内存管理的方法命名规则

在ARC无效时,用于对象生成/持有的方法必须遵循如下命名规则:以alloc/new/copy/mutablCopy开始的方法在返回对象时,必须返回给调用方所应当持有的对象。

在ARC有效时,除了上述规则外,增长init规则。

以init开始的方法的规则要比alloc/new/copy/mutableCopy更严格。该方法必须是实例方法,而且必需要返回对象。返回的对象应该为id类型或者该方法声明类的对象类型。

id obj = [[NSObject alloc] init];

如上所示,init方法会初始化alloc方法返回的对象,而后原封不动地返还给调用方。

注:initialize不包含在上述命名规则里。

不要显式调用dealloc

不管ARC是否有效,只要对象的全部者都不持有该对象,该对象就被废弃。对象被废弃时,不管ARC是否有效,都会调用对象的dealloc方法。在ARC无效时,必须调用[super dealloc]。ARC有效时会遵循没法显式调用dealloc这一规则,ARC对此会自动进行处理,dealloc中只需技术废弃对象时所必须的处理,好比删除已注册的代理或者观察者对象。

使用@autoreleasepool块代替NSAutoreleasePoll

不能使用区域(NSZone)

对象型变量不能做为C语言结构体的成员

struct Data{
    NSMutableArray * array;
}
复制代码

编译后

error:ARC forbids Objective-C objects in struct

由于C语言的规约上没有方法来管理结构体成员的生存周期。

要把对象型变量假如到结构体成员中,可强制转换为void*或者是附加__unsafe_unretained修饰符。(附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象)

显式转换id和void*

ARC无效时,id变量和void*变量相互赋值没有问题,可是ARC有效时则会引发编译错误。

在ARC有效时,id型或对象型变量赋值给void*或者逆向赋值时都须要进行特定的转换。若是想单纯地赋值,可使用__bridige转换。

id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id o = (__bridge id)p;
复制代码

可是转换为void*的__bridge转换,其安全性与赋值给__unsafe_unretained修饰符相近,甚至会更低。若是管理时不注意赋值对象的全部者,就会因悬垂指针而致使程序崩溃。

__bridge转换还有其余两种:__bridge_retained转换和__bridge_transfer转换

__bridge_retained转换可以使要转换赋值的变量也持有所赋值的对象。__bridge_retained转换与retain相相似。

void *p = 0;
{
    id obj = [[NSObject alloc] init];
    p = (__bridge_retained void *)obj;
}
NSLog(@"class = %@",[(__bridge id)p class]);
复制代码

运行结果为: 2017-12-06 16:39:06.148058+0800 ImageOrientation[7685:2312365] class = NSObject

变量做用域结束时,虽然随着持有强引用的变量obj失效,对象随之释放,但因为__bridge_retained转换使变量p看上去持有该对象的状态,所以该对象不会被废弃。

ARC无效时实现以上逻辑的代码为:

void *p = 0;
{
    id obj = [[NSObject alloc] init];
    /** [obj retainCount] ->1 */
    p = [obj retain];
    /** [obj retainCount] ->2 */
    [obj release];
    /** [obj retainCount] ->1 */
}
/*
 * [(id)p retainCount] ->1
 * 即[obj retainCount] ->1
 * 对象仍存在
 */
复制代码

__bridge_transfer转换,被转换的变量所持有的对象在该变量被赋值给转换目标变量后随之释放。__bridge_transfer转换与release相相似。

id obj = (__bridge_transfer id)p;至关于ARC无效时代码以下:

/**ARC无效*/
id obj = (id)p;
[obj retain];
[p release];
复制代码

__bridge转换,__bridge_retained转换,__bridge_transfer转换经常使用于OC对象和CoreFoundation对象之间的相互变换中。

  1. __bridge:CF和OC对象转化时只涉及对象类型不涉及对象全部权的转化;
  2. __bridge_transfer:经常使用在讲CF对象转换成OC对象时,将CF对象的全部权交给OC对象,此时ARC就能自动管理该内存;(做用同CFBridgingRelease())
  3. __bridge_retained:(与__bridge_transfer相反)经常使用在将OC对象转换成CF对象时,将OC对象的全部权交给CF对象来管理;(做用同CFBridgingRetain())

如下函数可用于OC对象和CoreFoundation对象之间的相互变换,成为Toll-Free Bridge "免费桥"转换。

CFTypeRef CFBridgingRetain(id X){
    return (__bridge_retained CFTypeRef)X;
}

id CFBridgingRelease(CFTypeRef X){
    return (__bridge_transfer id)X;
}
复制代码

下例为将生成并持有的NSMutableArray对象做为CoreFoundation对象来处理。

CFMutableArrayRef cfObject = NULL;
{
    id obj = [[NSMutableArray alloc] init
    /**变量obj持有对生成并持有对象的强引用 */
    
    cfObject = CFBridgingRetain(obj);
    /** 经过CFBridgingRetain将对象CFRetain赋值给变量cfObject */
    
    CFShow(cfObject);
    printf("retain count =%d\n",CFGetRetainCount(cfObject));
    /** 经过变量obj的强引用和经过CFBridgingRetain,引用计数为2
}
/** 由于变量obj超出做用范围,因此其强引用失效,引用计数为1 */
printf("retain count after the scope =%d\n",CFGetRetainCount(cfObject));

CFRelease(cfObject);
/** 由于将对象CFRelease,因此其引用计数为0,古该对象被废弃。*/


复制代码

运行结果为:

(
)
retain count =2
retain count after the scope =1
复制代码

由此可知,Foundation框架的API生成并持有的OC对象可以做为CF对象来使用,也能够经过CFRelease来释放。以上代码也能够用__bridge_retained转换来代替。

CFMutableArrayRef cfObject = (__bridge_retained CFMutableArrayRef) obj;

若是使用__bridge转换,第一句打印的结果是1,由于__bridge转换不改变对象的持有情况,obj持有NSMutableArray对象的强引用,因此为1.后一句打印crash,由于obj超出其做用域,因此强引用失效,对象释放,无持有者的对象被废弃,因此出现悬垂指针,致使崩溃。

那么,将CoreFoundation的API生成并持有对象,将该对象做为NSMutableArray对象来处理,代码以下:

{
    CFMutableArrayRef cfObject = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
    printf("retain count =%d\n",CFGetRetainCount(cfObject));
    /*
     * CoreFoundation框架的API生成并持有对象
     * 以后的对象引用计数为1
     */
    id obj = CFBridgingRelease(cfObject);
    /*
     * 经过CFBridgingRelease赋值,变量obj持有对象强引用的同时,对象经过CFRelease释放
     */
    
    printf("retain count after the cast =%d\n",CFGetRetainCount(cfObject));
    /*
     * 由于只有变量obj持有对生成并持有对象的强引用,因此引用计数为1
     * 另外,经由CFBridgingRelease转换后,赋值给变量cfObject中的指针也指向仍在存在的对象,因此能够正常使用。
     */
    NSLog(@"class =%@",obj);
    
}
/*
 * 由于变量obj超出做用域,因此其强引用失效,对象获得释放,无全部者的对象随之被废弃。
 */
复制代码

运行结果以下:

retain count =1
retain count after the cast =1
2017-12-06 21:30:02.473804+0800 ImageOrientation[8249:2573155] class =(
)
复制代码

也可使用__bridge_transfer转换代替CFBridgingRelease。

id obj = (__bridge_transfer id)cfObject;
复制代码

若是使用__bridge来替代__bridge_transfer或CFBridgingRelease转换,则第一句打印为1,中间打印为2,由于obj和cfObject同时持有对象的强引用,因此为2。超出范围后obj强引用失效,对象的引用计数为1。


ARC属性

ARC有效时,Objective-C类的属性也会发生变化。具体以下因此:

属性声明的属性 全部权修饰符
assign __unsafe_unretained修饰符
copy __strong修饰符(可是赋值的是被复制的对象)
ratai __strong修饰符
unsafe_unretained __unsafe_unretained修饰符
weak __weak修饰符

以上各属性复制给指定的属性中就想弹鼓赋值给附加各属性对应的全部权修饰符的变量中。只有copy属性不是简单的赋值,他赋值的是经过NSCopying接口的copyWithZone:方法复制复制源所生成的对象。

相关文章
相关标签/搜索