循环引用原理
- 视图控制器持有Block:视图控制器定义了一个Block属性,在ARC(自动引用计数)环境下,默认是强引用,即视图控制器对Block有一个强引用。
- Block强引用视图控制器:当Block内部使用了视图控制器的实例变量、方法等,Block会自动捕获视图控制器,并且在ARC下对视图控制器形成强引用。
- 循环引用形成:这样就形成了一个循环,视图控制器通过强引用持有Block,而Block又通过强引用持有视图控制器,导致两者都无法被释放,造成内存泄漏。
解决方案及优缺点分析
方案一:使用__weak修饰符
- 实现方式:在Block内部使用__weak修饰的视图控制器弱引用。例如:
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) {
// 使用strongSelf访问视图控制器的属性和方法
}
};
- 优点:
- 简单直接,有效解决循环引用问题。
- 对原代码侵入性较小,在大多数情况下容易实现。
- 缺点:
- 需要手动管理弱引用转强引用的过程,在多线程环境下,如果在
if (strongSelf)
判断之后,strongSelf
被释放,可能会出现潜在的问题。
- 如果不小心忘记在Block内部将弱引用转为强引用,可能会导致对象在使用过程中被提前释放,出现野指针错误。
方案二:使用__unsafe_unretained修饰符
- 实现方式:类似于__weak,在Block内部使用__unsafe_unretained修饰的视图控制器引用。例如:
__unsafe_unretained typeof(self) unsafeSelf = self;
self.block = ^{
// 使用unsafeSelf访问视图控制器的属性和方法
};
- 优点:
- 同样能解决循环引用问题,并且在语法上比__weak更简洁,不需要手动将弱引用转为强引用。
- 性能比__weak略高,因为__weak需要runtime维护一个弱引用表,而__unsafe_unretained不需要。
- 缺点:
- 存在野指针风险,因为__unsafe_unretained修饰的引用不会在对象释放时自动置为nil,当对象被释放后,指向该对象的指针就成为野指针,继续使用会导致程序崩溃。
- 在多线程环境下风险更高,由于不会自动置nil,可能在一个线程释放对象后,另一个线程还在使用野指针。
方案三:使用代理模式
- 实现方式:
- 定义一个协议,例如
ViewControllerDelegate
。
- 视图控制器遵守该协议,并实现相关方法。
- Block中调用代理的方法,而不是直接访问视图控制器的属性和方法。
- 在视图控制器中设置代理为自身。
@protocol ViewControllerDelegate <NSObject>
- (void)doSomething;
@end
@interface ViewController : UIViewController <ViewControllerDelegate>
@property (nonatomic, strong) id<ViewControllerDelegate> delegate;
@property (nonatomic, copy) void(^block)();
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.delegate = self;
self.block = ^{
[self.delegate doSomething];
};
}
- (void)doSomething {
// 具体实现
}
@end
- 优点:
- 从设计模式角度看,代码结构更清晰,符合单一职责原则,将视图控制器的部分功能委托给代理处理。
- 解决循环引用的同时,增强了代码的可维护性和可扩展性,例如可以很方便地将代理设置为其他对象,实现不同的业务逻辑。
- 缺点:
- 增加了代码量,需要定义协议、设置代理等一系列操作,对于简单场景可能显得过于繁琐。
- 代理关系的维护需要小心,例如在视图控制器生命周期内确保代理对象的正确设置和释放,否则可能导致空指针调用等问题。