MST
星途 面试题库

面试题:Objective-C 中的内存管理机制相关问题

在Objective-C中,简述ARC(自动引用计数)和MRC(手动引用计数)的区别,并举例说明在ARC环境下可能导致内存泄漏的场景及如何排查和解决。
12.4万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

ARC(自动引用计数)和MRC(手动引用计数)的区别

  1. 内存管理方式
    • MRC:开发者需要手动调用retainreleaseautorelease等方法来管理对象的引用计数。例如,当创建一个对象时,需要手动调用retain增加引用计数,在不需要该对象时调用release减少引用计数,当对象被释放时,其占用的内存会被回收。
    • ARC:编译器会自动在适当的位置插入retainreleaseautorelease等方法。开发者无需手动管理对象的引用计数,大大减轻了开发负担,降低了因手动管理不当导致内存泄漏或悬空指针的风险。
  2. 代码简洁性
    • MRC:代码中充斥着大量与内存管理相关的代码,使得代码冗长且易出错。例如:
NSObject *obj = [[NSObject alloc] init];
[obj retain];
// 使用obj
[obj release];
- **ARC**:代码更加简洁,专注于业务逻辑。例如:
NSObject *obj = [[NSObject alloc] init];
// 使用obj,无需手动release
  1. 内存管理时机
    • MRC:开发者必须精确把握对象的生命周期,在合适的时机调用release方法,否则容易导致内存泄漏或过早释放对象。
    • ARC:编译器会根据对象的作用域和引用关系,自动确定对象的释放时机,使得内存管理更加安全和可靠。

ARC环境下可能导致内存泄漏的场景及排查和解决方法

  1. 循环引用
    • 场景:两个或多个对象相互持有对方的强引用,形成循环,导致对象无法被释放。例如,一个视图控制器ViewController持有一个自定义视图CustomView的强引用,而CustomView又持有ViewController的强引用。
@interface ViewController : UIViewController
@property (nonatomic, strong) CustomView *customView;
@end

@interface CustomView : UIView
@property (nonatomic, strong) ViewController *viewController;
@end
- **排查方法**:
    - 使用 Instruments 工具中的Leaks模板,运行应用程序,Leaks工具会检测到无法释放的对象,并显示其内存地址和相关调用栈信息,帮助定位循环引用的位置。
    - 通过代码审查,检查对象之间的引用关系,特别是存在相互引用的情况。
- **解决方法**:
    - 将其中一个强引用改为弱引用(`weak`)或无主引用(`unowned`)。例如,将`CustomView`中的`viewController`属性改为`weak`:
@interface CustomView : UIView
@property (nonatomic, weak) ViewController *viewController;
@end
    - 使用`__block`修饰符(在使用闭包时),并在合适的时机手动断开引用。例如:
__block ViewController *weakSelf = self;
self.customView.block = ^{
    [weakSelf doSomething];
    weakSelf = nil;
};
  1. NSTimer 导致的内存泄漏
    • 场景:当NSTimerself作为目标时,如果NSTimer没有被及时 invalidate,NSTimer会一直持有self的强引用,导致self无法被释放。
@interface ViewController : UIViewController
@property (nonatomic, strong) NSTimer *timer;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateUI) userInfo:nil repeats:YES];
}
- (void)updateUI {
    // 更新UI
}
@end
- **排查方法**:同样可以使用 Instruments 工具中的Leaks模板,检测到无法释放的`ViewController`对象,并查看其引用关系,确定是`NSTimer`导致的问题。
- **解决方法**:
    - 在`ViewController`的`dealloc`方法中调用`[self.timer invalidate]; self.timer = nil;`,确保在`ViewController`被释放前,`NSTimer`被停止并取消对`self`的引用。
    - 使用`weak`引用来创建`NSTimer`,例如:
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
    [weakSelf updateUI];
}];
  1. Block 内部对 self 的强引用
    • 场景:在 Block 内部使用self时,如果没有使用弱引用,Block 会持有self的强引用,当 Block 的生命周期比self长时,会导致self无法被释放。
@interface ViewController : UIViewController
@property (nonatomic, strong) void (^block)(void);
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.block = ^{
        NSLog(@"%@", self);
    };
}
@end
- **排查方法**:通过代码审查,查看 Block 内部是否直接使用了`self`,如果没有使用弱引用,就可能存在内存泄漏风险。也可以借助 Instruments 工具检测无法释放的`ViewController`对象,并分析其引用关系。
- **解决方法**:使用`weak`或`unowned`引用来代替`self`。例如:
__weak typeof(self) weakSelf = self;
self.block = ^{
    NSLog(@"%@", weakSelf);
};

或者在 ARC 环境下,对于确定不会产生悬空指针的情况,可以使用unowned

__unowned typeof(self) unownedSelf = self;
self.block = ^{
    NSLog(@"%@", unownedSelf);
};