面试题答案
一键面试循环引用的形成
- 场景设定:假设有一个复杂的视图控制器
ViewController
,其中包含一个自定义视图CustomView
。ViewController
持有CustomView
的实例作为属性,CustomView
中有一个代码块属性completionBlock
,并且CustomView
在某个操作完成后会调用这个代码块。 - 代码示例:
#import <UIKit/UIKit.h>
@interface CustomView : UIView
@property (nonatomic, copy) void (^completionBlock)();
@end
@implementation CustomView
- (void)doSomeWork {
// 模拟工作完成后调用代码块
if (self.completionBlock) {
self.completionBlock();
}
}
@end
@interface ViewController : UIViewController
@property (nonatomic, strong) CustomView *customView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.customView = [[CustomView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:self.customView];
__weak typeof(self) weakSelf = self;
self.customView.completionBlock = ^{
// 这里如果使用 `self` 而不是 `weakSelf`,就会形成循环引用
[weakSelf.customView doSomeWork];
};
}
@end
- 形成原理:
ViewController
持有CustomView
,而CustomView
的代码块completionBlock
又持有ViewController
(如果在代码块中直接使用self
)。这样就形成了一个循环引用,ViewController
->CustomView
->completionBlock
->ViewController
,导致内存无法释放。
解决方案及优缺点分析
- 使用 __weak 修饰符:
- 方案:如上述代码示例中,在代码块外使用
__weak typeof(self) weakSelf = self;
,然后在代码块内使用weakSelf
。这样代码块不会强引用ViewController
,从而打破循环引用。 - 优点:简单直接,在大多数情况下能有效解决循环引用问题,不会引入过多复杂逻辑。
- 缺点:如果在代码块执行期间
ViewController
被释放,weakSelf
会变为nil
,可能导致空指针访问问题,需要在代码块中注意检查weakSelf
是否为nil
。
- 方案:如上述代码示例中,在代码块外使用
- 使用 __block 修饰符(配合手动释放):
- 方案:
#import <UIKit/UIKit.h>
@interface CustomView : UIView
@property (nonatomic, copy) void (^completionBlock)();
@end
@implementation CustomView
- (void)doSomeWork {
if (self.completionBlock) {
self.completionBlock();
}
}
@end
@interface ViewController : UIViewController
@property (nonatomic, strong) CustomView *customView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.customView = [[CustomView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:self.customView];
__block typeof(self) blockSelf = self;
self.customView.completionBlock = ^{
[blockSelf.customView doSomeWork];
blockSelf = nil; // 手动释放对 self 的引用
};
}
@end
- **优点**:能解决循环引用问题,并且在代码块内可以直接使用类似 `self` 的变量,相对简洁。
- **缺点**:需要手动在代码块结束时将 `blockSelf` 置为 `nil`,如果忘记这一步,仍然可能存在循环引用。而且在ARC环境下,对 `__block` 变量的内存管理相对复杂一些。
3. 在合适时机手动断开引用:
- 方案:在 ViewController
的 dealloc
方法中,将 CustomView
的 completionBlock
置为 nil
。
#import <UIKit/UIKit.h>
@interface CustomView : UIView
@property (nonatomic, copy) void (^completionBlock)();
@end
@implementation CustomView
- (void)doSomeWork {
if (self.completionBlock) {
self.completionBlock();
}
}
@end
@interface ViewController : UIViewController
@property (nonatomic, strong) CustomView *customView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.customView = [[CustomView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:self.customView];
self.customView.completionBlock = ^{
[self.customView doSomeWork];
};
}
- (void)dealloc {
self.customView.completionBlock = nil;
}
@end
- **优点**:逻辑清晰,明确在 `ViewController` 释放时断开循环引用。
- **缺点**:需要在每个可能产生循环引用的地方手动处理,容易遗漏。如果有多个地方持有循环引用,维护成本较高。