MST

星途 面试题库

面试题:Objective-C中复杂场景下Block内存循环引用及跨层传递问题

假设有一个多层嵌套的视图结构,在不同层级的类中存在Block的传递和使用,并且在这些Block中存在对不同层级对象的引用,这种复杂场景下如何避免和解决内存循环引用?请从设计模式、内存管理机制以及代码结构等方面阐述你的思路,并给出一个完整的可运行的代码示例,同时说明在不同运行时环境(如iOS不同版本)下可能需要注意的差异。
45.3万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

避免和解决内存循环引用的思路

  1. 设计模式方面
    • 代理模式:可以使用代理模式替代部分Block的使用。例如,在多层嵌套视图结构中,当某个层级的类需要通知上级类某个事件时,可以通过代理方法来实现。这样,上级类持有代理对象(通常是弱引用),而不是通过Block持有可能导致循环引用的对象。
    • 回调函数(非Block形式):对于一些简单的通知场景,可以使用传统的函数指针回调。这样可以避免Block在捕获对象时可能带来的循环引用问题。
  2. 内存管理机制方面
    • 弱引用:在Block中对可能导致循环引用的对象使用弱引用。例如在Objective - C中,可以使用__weak关键字修饰对象。在Swift中,可以使用weak关键字。这样,Block捕获对象时不会增加对象的引用计数,从而避免循环引用。
    • 自动释放池:合理使用自动释放池,在适当的时机释放临时对象,减少内存占用,也有助于避免因内存管理不当导致的潜在循环引用问题。
  3. 代码结构方面
    • 解耦代码:尽量减少不必要的对象间强引用关系。将复杂的逻辑拆分到不同的类或模块中,使得对象之间的依赖关系更加清晰和简单,从而降低循环引用发生的可能性。
    • 合理的生命周期管理:确保对象在其生命周期结束时,相关的引用能够被正确释放。例如,在视图控制器的dealloc方法中,取消注册可能存在的Block回调等操作。

完整可运行的代码示例(以Objective - C为例)

#import <UIKit/UIKit.h>

// 最内层的视图类
@interface InnerView : UIView
@property (nonatomic, copy) void (^innerBlock)(void);
@end

@implementation InnerView
- (void)dealloc {
    NSLog(@"InnerView dealloc");
}
@end

// 中间层的视图类
@interface MiddleView : UIView
@property (nonatomic, copy) void (^middleBlock)(void);
@end

@implementation MiddleView
- (void)dealloc {
    NSLog(@"MiddleView dealloc");
}
@end

// 最外层的视图控制器类
@interface ViewController : UIViewController
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    InnerView *innerView = [[InnerView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
    MiddleView *middleView = [[MiddleView alloc] initWithFrame:CGRectMake(50, 50, 200, 200)];
    [middleView addSubview:innerView];
    [self.view addSubview:middleView];
    
    __weak typeof(self) weakSelf = self;
    innerView.innerBlock = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        if (strongSelf) {
            NSLog(@"Inner block accessed view controller: %@", strongSelf);
        }
    };
    
    __weak typeof(middleView) weakMiddleView = middleView;
    middleView.middleBlock = ^{
        __strong typeof(weakMiddleView) strongMiddleView = weakMiddleView;
        if (strongMiddleView) {
            NSLog(@"Middle block accessed middle view: %@", strongMiddleView);
        }
    };
}

- (void)dealloc {
    NSLog(@"ViewController dealloc");
}
@end

不同运行时环境(如iOS不同版本)下可能需要注意的差异

  1. iOS 4.3及之前:在iOS 4.3及之前的版本中,ARC(自动引用计数)还未引入。需要手动管理内存,在处理Block和对象引用时,要特别注意retainreleaseautorelease的正确使用,以避免内存泄漏和循环引用。
  2. iOS 5.0及之后:ARC开始引入,大大简化了内存管理。但在使用Block捕获对象时,仍需注意__weak(iOS 5.0开始支持)和__unsafe_unretained(iOS 5.0开始支持,不推荐使用,因为对象释放后指针不会自动置为nil)的正确使用,确保避免循环引用。同时,不同iOS版本在内存管理机制的优化上可能存在差异,在性能敏感的场景下,需要注意测试和优化。例如,iOS 9及之后在内存管理和对象释放的效率上有一定的优化。

在Swift中,同样要注意weakunowned(类似__unsafe_unretained,但在对象释放时会导致运行时错误)的使用,不同版本Swift对内存管理和闭包(类似Block)的优化也可能会影响代码的性能和循环引用处理。