MST

星途 面试题库

面试题:Objective-C 的内存管理机制及优化

阐述 Objective-C 的自动引用计数(ARC)原理,以及在 ARC 环境下,循环引用是如何产生的,有哪些常见的场景(如代理、block 等)。针对这些循环引用场景,应该如何解决?另外,如果在某些特殊情况下需要手动管理内存(比如与 Core Foundation 框架交互),应该注意什么?
31.1万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

Objective-C 的自动引用计数(ARC)原理

ARC 是一种自动内存管理机制。编译器会在编译期自动在合适的位置插入 retainreleaseautorelease 等内存管理方法。当对象的引用计数变为 0 时,ARC 会自动调用对象的 dealloc 方法释放其所占用的内存。

ARC 环境下循环引用的产生

  1. 对象之间相互强引用:当两个或多个对象之间相互持有对方的强引用时,就会形成循环引用。因为每个对象的引用计数都不会降为 0,导致这些对象所占用的内存无法被释放。

常见的循环引用场景及解决方法

  1. 代理(Delegate)场景
    • 产生原因:一般情况下,视图控制器(ViewController)持有视图(View)的强引用,而视图会将视图控制器设置为代理(delegate),如果代理属性使用 strong 修饰,就会形成循环引用。因为视图控制器持有视图,视图又强引用视图控制器。
    • 解决方法:将代理属性声明为 weak 或者 unsafe_unretainedweak 修饰的属性不会增加对象的引用计数,并且当对象被释放时,指向该对象的 weak 指针会自动被设置为 nil,避免野指针问题。unsafe_unretained 同样不会增加引用计数,但当对象被释放时,指针不会被置为 nil,可能会导致野指针,所以一般优先使用 weak。例如:
@property (nonatomic, weak) id<MyViewDelegate> delegate;
  1. Block 场景
    • 产生原因:如果在 block 内部使用了外部对象(如视图控制器中的属性),并且 block 被该对象持有(比如作为属性),默认情况下,block 会对其捕获的外部对象进行强引用。如果该外部对象又持有这个 block,就会形成循环引用。例如:
self.block = ^{
    NSLog(@"%@", self.someProperty);
};
- **解决方法**:
    - **使用 `__weak` 修饰符**:在 block 外部使用 `__weak` 修饰外部对象,然后在 block 内部使用该弱引用对象。这样 block 不会强引用外部对象,避免循环引用。例如:
__weak typeof(self) weakSelf = self;
self.block = ^{
    NSLog(@"%@", weakSelf.someProperty);
};
    - **使用 `__block` 修饰符(ARC 环境下不常用,可能仍有循环引用风险)**:`__block` 修饰的变量在 block 内部是可变的,并且不会像默认情况那样强引用对象,但在 ARC 下,`__block` 修饰的对象可能仍会被 block 强引用,所以需要谨慎使用。例如:
__block MyViewController *blockSelf = self;
self.block = ^{
    NSLog(@"%@", blockSelf.someProperty);
    blockSelf = nil;
};

手动管理内存(与 Core Foundation 框架交互)注意事项

  1. 对象所有权桥接:在 Core Foundation(CF)和 Objective-C 之间交互时,要注意对象所有权的转换。例如,使用 CFBridgingRetain 函数将 Objective-C 对象转换为 Core Foundation 对象并获取其所有权,使用 CFBridgingRelease 函数将 Core Foundation 对象转换为 Objective-C 对象并释放 Core Foundation 对象的所有权。例如:
// 将 NSString 转换为 CFString 并获取所有权
CFStringRef cfString = CFBridgingRetain(@"Hello");
// 使用完后释放所有权并转换回 NSString
NSString *str = CFBridgingRelease(cfString);
  1. 引用计数匹配:对于 Core Foundation 对象,要确保 CFRetainCFRelease 的调用次数匹配。如果在获取对象所有权后没有正确释放,会导致内存泄漏。例如:
CFStringRef cfString = CFStringCreateWithCString(NULL, "World", kCFStringEncodingUTF8);
CFRetain(cfString);
// 操作 cfString
CFRelease(cfString);
  1. 内存管理规则一致性:在混合使用 Core Foundation 和 Objective-C 内存管理时,要保持内存管理规则的一致性。避免在同一个对象上同时使用 ARC 和手动引用计数管理,以免造成混乱和内存问题。