MST
星途 面试题库

面试题:Objective-C Block内存管理的深度剖析与优化

在一个复杂的Objective-C项目中,存在多个相互嵌套且生命周期复杂的Block,部分Block还持有大量数据。请从内存管理的角度分析可能出现的性能问题,并提出一套完整的优化方案,包括但不限于Block的声明、赋值、释放时机以及对持有对象的处理策略等。
35.1万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

可能出现的性能问题

  1. 循环引用
    • 当Block捕获了外部对象(如视图控制器实例),而Block又被该对象持有(例如作为属性),就会形成循环引用,导致对象无法释放,造成内存泄漏。例如,在视图控制器中定义一个Block属性,并在Block内部访问视图控制器的属性:
    @interface ViewController : UIViewController
    @property (nonatomic, copy) void (^block)(void);
    @end
    
    @implementation ViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.block = ^{
            NSLog(@"%@", self.title);
        };
    }
    @end
    
    • 这里self.block持有了self,而self又持有self.block,形成循环引用。
  2. 大量数据持有导致内存峰值过高
    • 由于部分Block持有大量数据,在Block的生命周期内,这些数据一直占用内存。如果Block的生命周期过长(例如被一个全局变量持有),可能会导致应用程序的内存峰值过高,甚至引发内存警告或应用崩溃。

优化方案

  1. 解决循环引用
    • 使用__weak关键字:在Block内部使用__weak修饰符来捕获外部对象,以打破循环引用。例如:
    @interface ViewController : UIViewController
    @property (nonatomic, copy) void (^block)(void);
    @end
    
    @implementation ViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
        __weak typeof(self) weakSelf = self;
        self.block = ^{
            __strong typeof(weakSelf) strongSelf = weakSelf;
            if (strongSelf) {
                NSLog(@"%@", strongSelf.title);
            }
        };
    }
    @end
    
    • 这里先使用__weak修饰符创建一个弱引用weakSelf,在Block内部再创建一个强引用strongSelf。这样在Block执行期间,如果self没有被释放,strongSelf会保持self的存在,执行完Block后,strongSelf释放,self有可能被释放,从而打破循环引用。
  2. 优化Block声明
    • 尽量使用__block而不是__strong:如果Block只是需要修改外部局部变量,使用__block修饰符,而不是让Block默认以强引用捕获变量。例如:
    - (void)testBlock {
        __block int num = 10;
        void (^block)(void) = ^{
            num = 20;
        };
        block();
        NSLog(@"%d", num);
    }
    
    • 这里使用__block修饰num,避免了不必要的强引用。
  3. 优化Block赋值
    • 适时赋值:在需要使用Block时再进行赋值,避免过早赋值导致Block过早持有对象。例如,在视图控制器的viewDidAppear:方法中赋值Block,而不是在viewDidLoad中,这样可以减少Block在视图控制器未显示时占用的内存。
    - (void)viewDidAppear:(BOOL)animated {
        [super viewDidAppear:animated];
        __weak typeof(self) weakSelf = self;
        self.block = ^{
            __strong typeof(weakSelf) strongSelf = weakSelf;
            if (strongSelf) {
                // 执行相关操作
            }
        };
    }
    
  4. 优化Block释放时机
    • 及时释放:当确定不再需要Block时,将其设置为nil。例如,在视图控制器的dealloc方法中:
    - (void)dealloc {
        self.block = nil;
    }
    
    • 这样可以确保Block及其持有的对象能够被及时释放。
  5. 对持有对象的处理策略
    • 减少不必要的持有:检查Block是否真的需要持有大量数据,如果可以在Block执行时再获取数据,尽量不要在Block创建时就持有。例如,对于一些网络请求结果数据,如果在Block执行时可以重新发起请求获取最新数据,就不要在Block创建时持有整个数据。
    • 使用自动释放池:如果Block内部有大量临时对象创建,可以在Block内部使用自动释放池来控制内存峰值。例如:
    self.block = ^{
        @autoreleasepool {
            // 创建大量临时对象的代码
            for (int i = 0; i < 10000; i++) {
                NSString *str = [NSString stringWithFormat:@"%d", i];
            }
        }
    };
    
    • 这样在自动释放池结束时,内部创建的临时对象会被释放,降低内存峰值。