面试题答案
一键面试循环引用在MRC下产生内存泄漏的原因
在MRC(Manual Reference Counting)环境中,对象的内存管理依赖手动调用retain
、release
和autorelease
方法。当ClassA和ClassB相互持有对方实例时,就会形成循环引用。例如:
@interface ClassA {
ClassB *b;
}
@property (nonatomic, retain) ClassB *b;
@end
@interface ClassB {
ClassA *a;
}
@property (nonatomic, retain) ClassA *a;
@end
@implementation ClassA
@synthesize b;
@end
@implementation ClassB
@synthesize a;
@end
假设我们创建如下对象:
ClassA *a = [[ClassA alloc] init];
ClassB *b = [[ClassB alloc] init];
a.b = b;
b.a = a;
[a release];
[b release];
这里虽然对a
和b
都调用了release
,但由于它们相互持有,引用计数都不会降为0,导致对象无法释放,从而产生内存泄漏。
使用autorelease
打破循环引用
- 原理:
autorelease
会将对象添加到自动释放池,当自动释放池销毁时,会向池中的对象发送release
消息。通过合理使用autorelease
,可以改变对象引用计数的释放时机,从而打破循环引用。 - 实现方式:
ClassA *a = [[[ClassA alloc] init] autorelease];
ClassB *b = [[[ClassB alloc] init] autorelease];
a.b = b;
b.a = a;
// 当自动释放池销毁时,a和b的引用计数会减少,有可能打破循环引用
- 优缺点:
- 优点:实现相对简单,不需要引入新的变量类型。
- 缺点:依赖自动释放池的生命周期,可能会导致内存暂时得不到释放。而且没有从根本上解决循环引用问题,只是延迟了对象释放时机。
在MRC下模拟weak
效果打破循环引用
- 原理:
weak
引用不会增加对象的引用计数,当对象被释放时,指向该对象的weak
指针会自动被设置为nil
。在MRC下可以通过自定义的方式模拟类似效果。 - 实现方式:
- 首先定义一个类似
WeakRef
的结构体来模拟weak
引用:
- 首先定义一个类似
typedef struct {
id target;
NSUInteger *refCount;
} WeakRef;
- 在ClassA和ClassB中使用这个结构体来替代强引用:
@interface ClassA {
WeakRef weakB;
}
@property (nonatomic) WeakRef weakB;
@end
@interface ClassB {
WeakRef weakA;
}
@property (nonatomic) WeakRef weakA;
@end
- 实现`setter`方法来管理`weak`引用:
@implementation ClassA
@synthesize weakB;
- (void)setWeakB:(WeakRef)newB {
if (weakB.target) {
(*weakB.refCount)--;
if (*weakB.refCount == 0) {
free(weakB.refCount);
}
}
weakB = newB;
if (weakB.target) {
if (!weakB.refCount) {
weakB.refCount = (NSUInteger *)malloc(sizeof(NSUInteger));
*weakB.refCount = 1;
} else {
(*weakB.refCount)++;
}
}
}
@end
@implementation ClassB
@synthesize weakA;
- (void)setWeakA:(WeakRef)newA {
if (weakA.target) {
(*weakA.refCount)--;
if (*weakA.refCount == 0) {
free(weakA.refCount);
}
}
weakA = newA;
if (weakA.target) {
if (!weakA.refCount) {
weakA.refCount = (NSUInteger *)malloc(sizeof(NSUInteger));
*weakA.refCount = 1;
} else {
(*weakA.refCount)++;
}
}
}
@end
- 优缺点:
- 优点:从根本上解决了循环引用问题,不会增加对象的引用计数,对象可以正常释放。
- 缺点:实现较为复杂,需要手动管理引用计数的辅助变量,增加了代码复杂度和出错风险。