MST

星途 面试题库

面试题:Objective-C 复杂场景下代码块循环引用检测

假设有一个视图控制器 `ViewController`,其中有一个自定义的视图类 `CustomView`,`CustomView` 中有一个代码块属性 `block`,该代码块会调用 `ViewController` 的某个方法。请详细说明这里可能出现的循环引用情况,以及如何完整地检测和解决这种复杂结构下的循环引用问题,包括内存管理方面的注意事项。
16.0万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

循环引用情况分析

  1. 循环引用产生原因
    • ViewController 持有 CustomView 实例,而 CustomViewblock 属性又强引用 ViewController 的情况下,会形成循环引用。例如,ViewController 代码如下:
#import "ViewController.h"
#import "CustomView.h"

@interface ViewController ()
@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.block = ^{
        [weakSelf someMethod];
    };
}

- (void)someMethod {
    // 具体实现
}

@end
  • CustomView 中:
#import "CustomView.h"

@interface CustomView ()
@property (nonatomic, copy) void(^block)();
@end

@implementation CustomView

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        // 初始化相关操作
    }
    return self;
}

- (void)someAction {
    if (self.block) {
        self.block();
    }
}

@end
  • ViewController 持有 CustomViewCustomViewblock 又强引用 ViewController 时,ViewController 因为持有 CustomView 不能释放,CustomView 因为 block 强引用 ViewController 也不能释放,从而导致循环引用。
  1. 可能的复杂情况
    • 如果 CustomView 中还有其他对象,这些对象也通过某些方式间接引用 ViewController,会使循环引用结构更加复杂。比如 CustomView 中有一个 NSObject 子类的实例 subObjectsubObject 持有一个 block,这个 block 又引用 ViewController,就形成了更复杂的循环引用路径。

检测循环引用的方法

  1. 工具检测
    • ** Instruments 的 Leaks 工具**:在 Xcode 中,可以使用 Instruments 的 Leaks 工具来检测内存泄漏,内存泄漏往往是循环引用的一个表现。运行应用程序,在 Instruments 中选择 Leaks 模板,当应用程序出现内存泄漏时,Leaks 工具会显示泄漏的对象以及相关的堆栈信息,通过分析堆栈信息可以定位可能存在循环引用的代码位置。
    • MLeaksFinder:这是一个开源的 iOS 内存泄漏检测工具。在开发环境中集成 MLeaksFinder 后,当页面消失时,它会检测是否有未释放的视图控制器等对象,如果有,则可能存在循环引用,并给出相应提示。
  2. 代码分析
    • 仔细检查代码中对象之间的引用关系,特别是属性的声明和赋值。查看是否存在相互强引用的情况,对于 block 的使用,重点检查 block 内部是否对外部对象进行了强引用。

解决循环引用问题

  1. 使用弱引用
    • 如上述代码中,在 ViewController 设置 block 时,使用 __weak 修饰符创建一个弱引用 weakSelf,然后在 block 内部使用 weakSelf 来调用 ViewController 的方法。这样,blockViewController 是弱引用,不会导致循环引用。当 ViewController 要释放时,由于 block 是弱引用,不会阻止 ViewController 的释放,ViewController 释放后,CustomView 也可以正常释放。
  2. 使用 __block 修饰符(较少用且需谨慎)
    • __block 修饰符在 ARC 环境下对对象的引用是弱引用(但在 MRC 下是强引用,需要注意)。例如:
__block typeof(self) blockSelf = self;
self.customView.block = ^{
    [blockSelf someMethod];
};
  • 但使用 __block 时要注意,在 block 执行完之前,如果 blockSelf 指向的对象被释放,blockSelf 会变为 nil,可能导致空指针异常,所以使用时需要特别谨慎。

内存管理方面的注意事项

  1. 属性声明
    • 对于 block 属性,应该使用 copy 修饰符,以确保 block 在不同上下文环境下的一致性和安全性。如 @property (nonatomic, copy) void(^block)();
  2. 生命周期管理
    • 要明确对象的生命周期,特别是 ViewControllerCustomView。当 ViewController 被销毁时,确保 CustomView 以及其内部可能存在的对 ViewController 的引用都能正确处理,避免悬空指针等问题。
  3. 避免过度引用
    • 在代码设计中,尽量避免不必要的对象之间的强引用,保持对象之间引用关系的清晰和简洁,减少循环引用出现的可能性。