MST

星途 面试题库

面试题:Objective-C MRC下的循环引用及解决机制

在Objective-C MRC编程中,假设存在两个类ClassA和ClassB,它们之间形成了循环引用。请详细分析这种循环引用在MRC环境下是如何产生内存泄漏的,并且阐述使用`autorelease`、`weak`(如果可以在MRC下模拟使用类似效果)等机制来打破循环引用的原理和具体实现方式,同时说明每种方式的优缺点。
32.2万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

循环引用在MRC下产生内存泄漏的原因

在MRC(Manual Reference Counting)环境中,对象的内存管理依赖手动调用retainreleaseautorelease方法。当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];

这里虽然对ab都调用了release,但由于它们相互持有,引用计数都不会降为0,导致对象无法释放,从而产生内存泄漏。

使用autorelease打破循环引用

  1. 原理autorelease会将对象添加到自动释放池,当自动释放池销毁时,会向池中的对象发送release消息。通过合理使用autorelease,可以改变对象引用计数的释放时机,从而打破循环引用。
  2. 实现方式
ClassA *a = [[[ClassA alloc] init] autorelease];
ClassB *b = [[[ClassB alloc] init] autorelease];
a.b = b;
b.a = a;
// 当自动释放池销毁时,a和b的引用计数会减少,有可能打破循环引用
  1. 优缺点
    • 优点:实现相对简单,不需要引入新的变量类型。
    • 缺点:依赖自动释放池的生命周期,可能会导致内存暂时得不到释放。而且没有从根本上解决循环引用问题,只是延迟了对象释放时机。

在MRC下模拟weak效果打破循环引用

  1. 原理weak引用不会增加对象的引用计数,当对象被释放时,指向该对象的weak指针会自动被设置为nil。在MRC下可以通过自定义的方式模拟类似效果。
  2. 实现方式
    • 首先定义一个类似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
  1. 优缺点
    • 优点:从根本上解决了循环引用问题,不会增加对象的引用计数,对象可以正常释放。
    • 缺点:实现较为复杂,需要手动管理引用计数的辅助变量,增加了代码复杂度和出错风险。