MST

星途 面试题库

面试题:Objective-C中Block循环引用及解决方法

在一个包含视图控制器的项目中,视图控制器持有一个Block属性,该Block又强引用了视图控制器本身,这会导致循环引用。请详细说明这种循环引用产生的原理,并给出至少两种有效的解决方案,且分析每种方案的优缺点。
14.9万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

循环引用原理

  1. 视图控制器持有Block:视图控制器定义了一个Block属性,在ARC(自动引用计数)环境下,默认是强引用,即视图控制器对Block有一个强引用。
  2. Block强引用视图控制器:当Block内部使用了视图控制器的实例变量、方法等,Block会自动捕获视图控制器,并且在ARC下对视图控制器形成强引用。
  3. 循环引用形成:这样就形成了一个循环,视图控制器通过强引用持有Block,而Block又通过强引用持有视图控制器,导致两者都无法被释放,造成内存泄漏。

解决方案及优缺点分析

方案一:使用__weak修饰符

  1. 实现方式:在Block内部使用__weak修饰的视图控制器弱引用。例如:
__weak typeof(self) weakSelf = self;
self.block = ^{
    __strong typeof(weakSelf) strongSelf = weakSelf;
    if (strongSelf) {
        // 使用strongSelf访问视图控制器的属性和方法
    }
};
  1. 优点
    • 简单直接,有效解决循环引用问题。
    • 对原代码侵入性较小,在大多数情况下容易实现。
  2. 缺点
    • 需要手动管理弱引用转强引用的过程,在多线程环境下,如果在if (strongSelf)判断之后,strongSelf被释放,可能会出现潜在的问题。
    • 如果不小心忘记在Block内部将弱引用转为强引用,可能会导致对象在使用过程中被提前释放,出现野指针错误。

方案二:使用__unsafe_unretained修饰符

  1. 实现方式:类似于__weak,在Block内部使用__unsafe_unretained修饰的视图控制器引用。例如:
__unsafe_unretained typeof(self) unsafeSelf = self;
self.block = ^{
    // 使用unsafeSelf访问视图控制器的属性和方法
};
  1. 优点
    • 同样能解决循环引用问题,并且在语法上比__weak更简洁,不需要手动将弱引用转为强引用。
    • 性能比__weak略高,因为__weak需要runtime维护一个弱引用表,而__unsafe_unretained不需要。
  2. 缺点
    • 存在野指针风险,因为__unsafe_unretained修饰的引用不会在对象释放时自动置为nil,当对象被释放后,指向该对象的指针就成为野指针,继续使用会导致程序崩溃。
    • 在多线程环境下风险更高,由于不会自动置nil,可能在一个线程释放对象后,另一个线程还在使用野指针。

方案三:使用代理模式

  1. 实现方式
    • 定义一个协议,例如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
  1. 优点
    • 从设计模式角度看,代码结构更清晰,符合单一职责原则,将视图控制器的部分功能委托给代理处理。
    • 解决循环引用的同时,增强了代码的可维护性和可扩展性,例如可以很方便地将代理设置为其他对象,实现不同的业务逻辑。
  2. 缺点
    • 增加了代码量,需要定义协议、设置代理等一系列操作,对于简单场景可能显得过于繁琐。
    • 代理关系的维护需要小心,例如在视图控制器生命周期内确保代理对象的正确设置和释放,否则可能导致空指针调用等问题。