MST

星途 面试题库

面试题:Objective-C中使用block导致循环引用的底层原理及优化

在Objective-C开发中,block经常被使用。请深入分析使用block时导致循环引用的底层原理,不仅要从内存管理角度,还要从ARC(自动引用计数)机制的工作原理方面进行剖析。并且针对不同场景下block引起的循环引用,给出多种优化方案及其适用场景。
30.2万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

循环引用底层原理

  1. 内存管理角度
    • 在Objective - C中,对象通过引用计数来管理内存。当一个对象被创建时,其引用计数为1。每次被其他对象强引用(持有),引用计数加1;每次不再被其他对象强引用(释放),引用计数减1。当引用计数变为0时,对象内存被释放。
    • 当block捕获外部变量时,如果捕获的是对象类型变量,默认会对该对象进行强引用。例如,在一个类的实例方法中定义block,block捕获了self,此时block会强引用self。如果self又强引用了block(比如将block作为属性持有),就形成了self -> block -> self这样的强引用环,导致self和block的引用计数都不会降为0,内存无法释放,造成循环引用。
  2. ARC机制角度
    • ARC(自动引用计数)会自动插入内存管理代码,比如retainreleaseautorelease。在循环引用场景下,ARC无法自动打破这个强引用环。因为ARC的工作是基于对象之间的引用关系,它不知道如何打破这种互相强引用的情况。例如,对于self -> block -> self的环,ARC会按照正常的引用计数规则增加和减少引用计数,但由于这个环的存在,引用计数永远不会变为0。

优化方案及其适用场景

  1. 使用__weak关键字
    • 适用场景:适用于绝大多数场景,尤其是在类的实例方法中使用block且可能出现循环引用的情况。
    • 示例
__weak typeof(self) weakSelf = self;
self.block = ^{
    __strong typeof(weakSelf) strongSelf = weakSelf;
    if (strongSelf) {
        // 使用strongSelf操作self相关内容
        [strongSelf doSomething];
    }
};
  • 原理__weak修饰的变量不会增加对象的引用计数,所以不会形成强引用环。这里先使用__weak创建一个弱引用weakSelf,在block内部为了防止weakSelf在block执行期间被释放,可以通过__strong将其提升为强引用strongSelf。这样既可以在block内安全地使用self相关内容,又避免了循环引用。
  1. 使用__unsafe_unretained关键字
    • 适用场景:在ARC环境下,与__weak类似,但适用于对性能要求较高且确定对象不会在block执行期间被释放的场景。因为__unsafe_unretained修饰的变量不会自动置为nil,如果对象提前释放,访问它会导致野指针错误。
    • 示例
__unsafe_unretained typeof(self) unsafeSelf = self;
self.block = ^{
    // 使用unsafeSelf操作self相关内容
    [unsafeSelf doSomething];
};
  • 原理__unsafe_unretained修饰的变量不增加对象的引用计数,与__weak不同的是,当对象被释放时,__unsafe_unretained修饰的指针不会自动置为nil,需要开发者自己确保对象在使用期间不会被释放。
  1. 在block执行完毕后手动打破循环引用
    • 适用场景:适用于block只执行一次且知道block何时执行完毕的场景,比如网络请求的回调block。
    • 示例
self.block = ^{
    // 执行网络请求等操作
    [self doNetworkRequestWithCompletion:^{
        // 网络请求完成
        self.block = nil;
    }];
};
  • 原理:在block执行完毕后,将持有block的属性(如这里的self.block)置为nil,打破self对block的强引用,从而打破循环引用。
  1. 使用中间对象打破循环引用
    • 适用场景:当直接使用__weak__unsafe_unretained不方便时,比如在复杂的类继承结构或多层嵌套的block场景下。
    • 示例:可以创建一个中间对象,这个中间对象不被self和block直接强引用,通过中间对象来传递数据,避免self和block之间的直接互相强引用。具体实现根据不同业务场景会有所不同。
    • 原理:通过引入中间对象,改变对象之间的引用关系,避免形成self -> block -> self这样的强引用环。