在objective-c中,内存的引用计数一直是一个让人比較头疼的问题。尤其是当引用计数涉及到arc、blocks等等的时候。似乎ARC的出现仅仅是让我们解放了双手,因为底层实现依旧依赖引用计数,所以开启ARC后,仅仅有对引用计数机制更加了解,才干避免Cycle Retain、Crash等问题的出现。

    可是因为使用ARC能够显著提高编码效率,所以建议尽量启用arc,本文内容也将以arc为主,全部測试等如未说明均表示开启arc。

    oc中内存的管理主要依赖引用计数,而对引用计数的影响又依赖修饰属性(暂且这么称呼),oc中经常使用的修饰属性例如以下:


属性

(1)修饰属性(使用@property定义时)

读写控制:

    readwrite:可读可写,会生成getter和setter方法。

    readonly:仅仅读,仅仅会生成getter方法,不会生成setter方法。

引用方式:

    copy:拷贝,复制一个对象并创建strong关联,引用计数为1 ,原来对象计数不变。

    assign:赋值,不涉及引用计数的变化,弱引用。ARC中对象不能使用assign,但原始类型(BOOL、int、float)仍然能够使用。

    retain:持有,对原对象引用计数加1,强引用。ARC中使用strong。

    weak:赋值(ARC),比assign多了一个功能,对象释放后把指针置为nil,避免了野指针。

    strong:持有(ARC),等同于retain。

线程安全:

    nonatomic:非原子操作,不加同步,多线程訪问可提高性能,但不是线程安全的。

    atomic:原子操作,与nonatomic相反。

  

(2)修饰变量(修饰不使用@property定义时,比方函数内的局部变量)

    __strong:是缺省的关键词,强引用。

    __weak:声明了一个能够自己主动置nil的弱引用(ARC中)。

    __unsafe_unretained:声明一个弱引用,可是不会自己主动nil化(仅仅有iOS 4 才应该使用)。

    __autoreleasing:用来修饰一个函数的參数,这个參数会在函数返回的时候被自己主动释放(类似autorelease)。


(3)默认的引用计数:

    alloc:分配对象,分配后引用计数为1。

    autorelease:对象引用计数减1,但假设为0不立即释放,等待近期一个pool时释放。

 

使用ARC


    ARC,全称叫AutomaticReference Counting,该机制从ios5開始開始导入。简单地说,就是代码中自己主动增加了retain/release。所以,其底层机制还是引用计数,所以掌握引用计数对内存管理依然很很重要,我甚至认为使用arc的前提就是充分了解引用计数机制,否则差点儿每天都要和Cycle Retain、Crash做斗争。

在你打开ARC时,你是不能使用retainrelease autorelease 操作的,原先须要手动加入的用来处理内存管理的引用计数的代码能够自己主动地由编译器完毕了,可是你须要在对象属性上使用weak 和strong, 当中strong就相当于retain属性,而weak相当于assign,基础类型还是使用assign。


(1)strong还是weak


    说究竟就是一个归属权的问题。小心出现循环引用导致内存无法释放,或者须要引用的对象过早被释放。大体上:IBOutlet能够为weak,NSString为copy或strong,Delegate一般为weak,基础类型用assign,只是要注意详细使用情况。

 

(2)outlet使用strong还是weak

    官方文档建议一般outlet属性都推荐使用weak,不是直接作为main view里面一个subview直接显示出来,而是须要通过实例化创建出来的view,应该使用 strong(自己创建的自己当然要保持引用了)。可是要注意使用 weak时不要丢失对象的全部权,否则应该使用strong。


(3)delegate使用strong还是weak

    delegate主要涉及到互相引用和crash(引用被释放)问题,为了防止这两个问题发生,delegate一般使用weak。先看代码:

//MyClassDelegate协议
@protocol MyClassDelegate <NSObject>
- (void)myClassOnSomeEvent:(MyClass*)myClass;
@end

//MyClass类
@interface MyClass
@property (weak,nonatomic)id<MyClassDelegate> delegate; // (1)这里使用weak
@end

@interface myViewController
//在myViewController中创建一个MyClass
@property (strong,nonatomic)MyClass *myClass;
@end

@implementation myViewController
- (void)someAction
{
myClass = [[MyClass alloc]init]; // (2)
myClass.delegate = self; // (3)
....
}
@end


在myViewController中,

运行myClass = [[MyClassalloc] init]; //(2),此时myViewController将会持有一个MyClass的引用。

运行myClass.delegate = self;//(3)时,myClass也会引用myViewController。 

 

1、当myClass.delegate使用weak时,

    不会出现互相引用问题,也不会出现crash(引用被释放)问题,

    引用关系例如以下(如果myViewController由Somebody持有):

objective-c启用ARC时的内存管理_内存管理

上图引用关系分析:

第一行:

    Somebody创建(持有)myViewController,所以myViewController的引用计数为1。

    myViewController持有myClass,所以myClass的引用计数为1。

    myClass通过成员delegate引用myViewController,但使用weak弱引用,所以引用计数不受影响。

第二行:

    假设Somebody释放myViewController,则myViewController引用计数减1,变成0,此时myViewController自己将会被释放(引用计数是0),并降低所持有对象(myClass)的引用计数。

第三行:

    myclass引用计数为0,被释放,因为是delegate是weak属性的,所以delegate将自己主动被设置为空。

 

2、当myClass.delegate使用strong时,

    会出现相互引用问题,导致对象无法被释放。

    引用关系例如以下:

 objective-c启用ARC时的内存管理_引用计数_02

第一行:

    Somebody创建(持有)myViewController,所以myViewController的引用计数为1。

    myViewController持有myClass,所以myClass的引用计数为1。

    myClass通过成员delegate引用myViewController,但使用strong引用,所以引用计数加1,变成2。

第二行:

    假设Somebody释放myViewController,则myViewController引用计数减1,变成1,此时myViewController不会被释放,myclass引用计数为1,也不会被释放,这样就造成了互相引用问题。

    并且没有外部对象在引用myViewController或myclass,造成了myViewController和myclass无法被释放。

 

3、当myClass.delegate使用weak时,假设有还有一个Somebody同一时候持有了myClass,

    因为weak能够自己主动置nil,所以不会出现crash(引用被释放)问题,

    则引用关系例如以下:

 objective-c启用ARC时的内存管理_弱引用_03

第一行:

    Somebody创建(持有)myViewController,所以myViewController的引用计数为1。

    myViewController持有myClass,所以myClass的引用计数为1。

    myClass通过成员delegate引用myViewController,但使用weak弱引用,所以引用计数不受影响。

第二行:

    新的Somebody持有同一个myClass,导致myClass引用计数加1,变成2。

第三行:

    假设Somebody释放myViewController,则myViewController引用计数减1,变成0,此时myViewController自己将会被释放(引用计数是0),并降低所持有对象(myClass)的引用计数。使myClass引用计数减1,变成1。因为myClass引用myViewController的delegate是weak属性的,所以delegate将自己主动被设置为空,不会出现crash(引用被释放)问题。

    注意,假设myClass引用myViewController的delegate是assign的话,则delegate不会被自己主动设置为空,将导致delegate再次调用myViewController时出错(myViewController已经释放了)。

第四行:

    Somebody正常持有myClass,假设此时Somebody释放myClass,则myClass引用计数减1并释放,不会出现不论什么问题。

 

 

关于block和引用计数

 

(1)修饰block

假设须要block在它被声明的作用域被销毁后继续使用的话,你就须要做一份拷贝。拷贝会把block移到堆里面。所以,使用@property时设置通常例如以下:

    @property(copy, nonatomic)void(^block)(void);

 

(2)retain cycle的问题

    block在实现时就会对它引用到的它所在方法中定义的栈变量进行一次仅仅读拷贝,然后在block块内使用该仅仅读拷贝。所以在使用block过程中,常常会遇到retain cycle的问题,比如:

- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:_observer];
}
- (void)loadView
{
[super loadView];

_observer = [[NSNotificationCenterdefaultCenter] addObserverForName:@"testKey"
object:nil
queue:nil
usingBlock:^(NSNotification*note)
{
[sselfdismissModalViewControllerAnimated:YES];
}];
}


    在block中用到了self,self会被block retain,而_observer会copy一份该block,就是说_observer间接持有self,同一时候当前的self也会retain_observer,终于导致self持有_observer,_observer持有self,形成retaincycle。

    对于在block中的retain cycle,在2011 WWDC Session #322 (Objective-C Advancements in Depth)有一个解决方式weak-strong dance,事实上现例如以下:

- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:_observer];
}
- (void)loadView
{
[super loadView];

__weakTestViewController *wself =self;
_observer =[[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"
object:nil
queue:nil
usingBlock:^(NSNotification *note)
{
TestViewController *sself = wself;
[sselfdismissModalViewControllerAnimated:YES];
}];
}


    在block中使用self之前先用一个__weak变量引用self,导致block不会retain self,打破retain cycle,然后在block中使用wself之前先用__strong类型变量引用wself,以确保使用过程中不会dealloc。简而言之就是推迟对self的retain,在使用时才进行retain。


(3)return一个block

    返回一个block时,ARC会自己主动将block加上autorelease,所以须要注意,假设运行过程中不能接受在runloop接受后才释放block,就须要自己增加@autoreleasepool块,可是測试发现64位iOS/mac时,系统会自己主动在使用结束后马上释放,32位则要等到runloop结束。

- (void)test
{
//@autoreleasepool{
AutoTest *a = [AutoTestsAutoTest];
NSLog(@“1”);
a = nil;
NSLog(@"2");
a = [[AutoTest alloc] init];
//}
NSLog(@"3");
}
- (IBAction)ok:(id)sender
{
[self test];
NSLog(@"4");
}


//运行结果

1释放23释放4   64位

123释放4释放   32位

12释放释放34   32位+@autoreleasepool


(4)block作为參数

    block作为參数时,假设使用范围超过了block的作用域(比方异步时,或者将block传递给其它对象等等),则须要copy此block,copy建议在使用此block的方法内实现(谁使用,谁管理),而不是在传递參数时copy。注意,block过一个strong类型的指针时,会自己主动copy。经过copy过的block会从栈空间移动到堆上,而且,copy一个已经在堆上的block时,此block不会受影响。


*假设想了解block的内存管理原则,建议查阅相关资料,了解一下block的内部实现方式。Block的内存管理非常难用简单的几句话来描写叙述。