面试题答案
一键面试循环引用导致内存泄漏的底层原理
在ARC环境下,对象的引用计数由编译器自动管理。当一个对象的引用计数降为0时,ARC会自动释放该对象占用的内存。然而,当两个或多个对象相互强引用,形成循环时,它们的引用计数永远不会降为0,从而导致内存泄漏。
例如,假设有两个类ClassA
和ClassB
,ClassA
中有一个ClassB
类型的属性,ClassB
中有一个ClassA
类型的属性,并且两个属性都被声明为strong
(强引用):
@interface ClassA : NSObject
@property (nonatomic, strong) ClassB *classB;
@end
@interface ClassB : NSObject
@property (nonatomic, strong) ClassA *classA;
@end
当在代码中创建这两个类的实例并相互引用时:
ClassA *a = [[ClassA alloc] init];
ClassB *b = [[ClassB alloc] init];
a.classB = b;
b.classA = a;
此时,a
和b
相互强引用,它们的引用计数都至少为1。即使超出了它们的作用域,由于循环引用的存在,它们的引用计数不会降为0,内存无法释放。
优化策略及代码示例
- 使用
weak
或unowned
弱引用- 原理:
weak
和unowned
修饰的属性不会增加对象的引用计数。weak
引用的对象在被释放后,指向该对象的weak
指针会自动被设置为nil
;unowned
引用的对象在被释放后,指向该对象的unowned
指针不会被设置为nil
,可能导致野指针,因此使用unowned
时需确保对象生命周期的一致性。 - 示例:
- 修改
ClassA
和ClassB
的属性声明,将其中一个改为weak
:
- 修改
- 原理:
@interface ClassA : NSObject
@property (nonatomic, strong) ClassB *classB;
@end
@interface ClassB : NSObject
@property (nonatomic, weak) ClassA *classA;
@end
- 使用代码:
ClassA *a = [[ClassA alloc] init];
ClassB *b = [[ClassB alloc] init];
a.classB = b;
b.classA = a;
// 当a和b超出作用域时,由于b对a是weak引用,不会形成循环引用,内存会被正常释放
- 利用
block
时使用__weak
或__block
- 原理:在
block
中使用对象时,如果block
被对象持有,而block
又强引用对象,可能导致循环引用。使用__weak
修饰对象,可以避免block
对对象的强引用;__block
在ARC下一般用于在block
中修改外部变量,但使用不当也可能导致循环引用,不过合理使用可以打破循环。 - 示例:
- 假设
ClassA
中有一个block
属性,且block
中使用了self
:
- 假设
- 原理:在
@interface ClassA : NSObject
@property (nonatomic, copy) void (^block)(void);
@end
@implementation ClassA
- (void)setupBlock {
__weak typeof(self) weakSelf = self;
self.block = ^{
typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) {
// 使用strongSelf进行操作,防止在block执行过程中self被释放
NSLog(@"Do something with self: %@", strongSelf);
}
};
}
@end
- 在这个示例中,
__weak typeof(self) weakSelf = self;
创建了一个弱引用weakSelf
,block
中使用weakSelf
避免了对self
的强引用,从而打破了可能的循环引用。如果需要在block
中确保self
在执行期间不被释放,可以像示例中那样先将weakSelf
提升为强引用strongSelf
。