这个会导致内存问题的 init 方法有什么问题?(苹果)



我试图了解以下代码有什么问题。

想象一下下面的类,"Foo":

@protocol FooDelegate <NSObject>
- (void)hereTakeThisFooBarDic:(NSDictionary *)fooBarDic;
@end
@interface Foo : NSObject <BarDelegate>
@property (nonatomic, strong) Bar *bar;
@property (nonatomic, weak) id <FooDelegate> *fooDelegate;
- (void)getFooBarDicForNum:(int)fooBarNum;
@end
@implementation Foo
static Foo *foo = nil;
- (id)init {
    if (!foo) {
        foo = [super init];
        self.bar = [[bar alloc] init];
    }
    return foo;
}
- (void)getFooBarDicForNum:(int)fooBarNum {
    self.bar.fooDelegate = self;
    [self.bar getFooBarDicFromIntarwebsNumber:fooBarNum];
}
//We get this callback from self.bar after a few ms
- (void)callbackWithFooBarDicFromIntarwebs:(NSDictionary *)fooBarDic {
    [self.fooDelegate hereTakeThisFooBarDic:fooBarDic];
}
@end

我们在代码中的某个地方调用Foo,如下所示:

   for(int i=0; i < 10; i++) {
       Foo *foo = [[Foo alloc] init];
       [foo getFooBarDicForNum:i];
   }

然后我们稍后在 hereTakeThisFooBarDic 方法中获取回调。

但问题是我们正在获得无限的内存增长。Foo的init方法似乎就像一个单例,但每次我们调用它时,我们都在为它分配更多的内存。不过,它不会注册为内存泄漏。但是,在查看此代码时,似乎不是执行单例的正确方法。

我想知道这段代码的作者做错了什么。

使用 ARC

时,您显示的init方法应该不会泄漏,至少在 Xcode 7.2/Clang 7.0.2 中不会泄漏(即编译器正确实现了 ARC 语义)。

但是,init方法并不"正确"(忽略任何多线程问题),即使它在这种情况下有效:

- (id)init {
    if (!foo) {
        foo = [super init];

上面的行似乎假设 [super init] 返回的值将与下一行中的self相同......

        self.bar = [[bar alloc] init];

它分配给self假设这与foo中的值相同......

    }
    return foo;

这样的假设不仅是错误的,而且这个非常init的方法就是一个例子,它可能不会返回它传递self

}

至少你没有写!

作者应该写的内容(如果遵循这种特定的"共享实例"模式)是大致如下的内容:

- (id)init
{
   if (!foo)
   {
      self = [super init];
      self.bar = [[Bar alloc] init];
      foo = self;
      return self;
   }
   return foo;
}

也就是说,这不是建议的共享实例模式。如果你对此以及如何制作现代单例模式感兴趣,有很多参考资料,我会给你指出这个,想不出为什么;-)

至于您怀疑的泄漏,它不是init,似乎也不是其他两种方法,因此您需要查看这些方法的调用。狩猎愉快!

每次调用 [[Foo alloc] init] 时,都会创建一个新Foo,但会被忽略。如果你没有使用 ARC 编译,这确实是一个泄漏:你已经分配了一些东西并且没有发布它。如果你是,它仍然很奇怪,我不确定如果它仍然是泄漏,我会感到惊讶。

如果需要以这种方式运行的单一实例,则必须仅分配一次,并确保后续调用+alloc返回同一实例。Peter Hosey有一篇非常有趣的博客文章。

最新更新