什么是循环强引用?比如说有两个对象,对象A 和对象 B,如果A强引用持有了B,B又强持有了A,这种情况下,就是循环强引用。我们会讨论2种通常会出现循环强引用的情形: blocks and delegates.
1. 代理 delegate
代理是OC中非常常用的模式,代理中,一个对象代表另一个对象执行相关操作。被代理对象持有一份代理对象的引用,当在合适的时机发送信息时,代理会通过改变UI进行响应。
API中得一个例子就是 UITableView 和它的 delegate. 这个示例中, table view 持有了一份代理的引用,代理对象持有了一份 table view的 引用,这就意味着相互引用,内存将无法释放。
如下自定义示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#import <Foundation/Foundation.h>
@class
ClassA;
@protocol
ClassA Delegate <
NSObject
>
-(
void
)classA:(ClassA *)classAObject didSomething:(
NSString
*)something;
@end
@interface
ClassA :
NSObject
@property
(nonatomic, strong)
id
<ClassADelegate> delegate;
@end
|
这在ARC环境下会产生一个T retain cycle,要避免这个问题,我们需要改变代理的引用属性为weak。
1
|
@property
(nonatomic, weak)
id
<ClassADelegate> delegate;
|
weak引用不会造成持有关系,不会保持对象的生命。如果没有对象持有代理对象和被代理对象,首先代理对象会销毁,然后被代理对象的强引用会被释放。如果没有指针再指向被代理对象,被代理对象会释放。
2.块 Blocks
block是代码块,类似于C函数,除了可执行代码之外,可能包含栈或堆中的变量。block因此保存了一系列的数据,这样可以在需要执行的时候进行使用,因为blocks 保持了代码执行需要的数据,主要用于回调。
Blocks 是 Objective C 对象, 但是有些内存管理原则只适用于 blocks, 不适于于Objective C 对象。
Blocks 保持了对所包含的对象的强引用,包括对象本身,所以很容易引起retain cycle. 如果一个对象包含一个属性block如下:
1
|
@property
(copy)
void
(^block)(
void
);
|
在它的实现中,你有一个这样的方法:
1
2
3
4
5
6
7
|
- (
void
)methodA {
self
.block = ^{
[
self
methodB];
};
}
|
这样就产生了self的强循环引用,对象self持有了block的强引用,block又持有了self的强引用。
提示:针对block属性,使用copy比较好,因为block需要copy,来保持与原始内存的分离。
为了避免循环强引用,我们使用 weak属性. 以下是代码:
1
2
3
4
5
6
7
8
9
|
- (
void
)methodA {
ClassB *
weakSelf =
self
;
self
.block = ^{
[weakSelf methodB];
};
}
|
通过对self的弱引用, block 不会保持与self的强关系,如果对象在block之前销毁,weakSelf 指针会被自动设置为nil,这样虽然很好的避免了内存问题,但是,指针是nil,我们的方法将不会被调用,block就不会实现所期望的行为,为了避免这种情况,我们将示例修改如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
- (
void
)methodA {
__weak
ClassB * weakSelf =
self
;
self
.block = ^{
__strong
ClassB *strongSelf = weakSelf;
if
(strongSelf) {
[strongSelf methodB];
}
};
}
|
我们在block内部创建了一个强持有的 self,这个引用会属于block保持,和block生命周期一样,它不会阻止 self 对象销毁,这样我们也避免了循环引用的问题。
不是所有的循环引用都像我上面的例子那么容易看出来,所有当你的block代码变得复杂的时候,你要考虑使用 weak 引用 。有2种情形容易出现循环引用,如上,通过使用weak 引用很容易打破循环引用,只要你能正确的识别出来,ARC虽然让内存管理便得容易了,但是我们仍然要对其保持警觉的思维。