面试题答案
一键面试ARC(自动引用计数)和MRC(手动引用计数)的区别
- 内存管理方式
- MRC:开发者需要手动调用
retain
、release
和autorelease
等方法来管理对象的引用计数。例如,当创建一个对象时,需要手动调用retain
增加引用计数,在不需要该对象时调用release
减少引用计数,当对象被释放时,其占用的内存会被回收。 - ARC:编译器会自动在适当的位置插入
retain
、release
和autorelease
等方法。开发者无需手动管理对象的引用计数,大大减轻了开发负担,降低了因手动管理不当导致内存泄漏或悬空指针的风险。
- MRC:开发者需要手动调用
- 代码简洁性
- MRC:代码中充斥着大量与内存管理相关的代码,使得代码冗长且易出错。例如:
NSObject *obj = [[NSObject alloc] init];
[obj retain];
// 使用obj
[obj release];
- **ARC**:代码更加简洁,专注于业务逻辑。例如:
NSObject *obj = [[NSObject alloc] init];
// 使用obj,无需手动release
- 内存管理时机
- MRC:开发者必须精确把握对象的生命周期,在合适的时机调用
release
方法,否则容易导致内存泄漏或过早释放对象。 - ARC:编译器会根据对象的作用域和引用关系,自动确定对象的释放时机,使得内存管理更加安全和可靠。
- MRC:开发者必须精确把握对象的生命周期,在合适的时机调用
ARC环境下可能导致内存泄漏的场景及排查和解决方法
- 循环引用
- 场景:两个或多个对象相互持有对方的强引用,形成循环,导致对象无法被释放。例如,一个视图控制器
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;
};
- NSTimer 导致的内存泄漏
- 场景:当
NSTimer
以self
作为目标时,如果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];
}];
- Block 内部对 self 的强引用
- 场景:在 Block 内部使用
self
时,如果没有使用弱引用,Block 会持有self
的强引用,当 Block 的生命周期比self
长时,会导致self
无法被释放。
- 场景:在 Block 内部使用
@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);
};