【问题标题】:What is wrong with this init method that would cause memory issues? (iOS)这种会导致内存问题的 init 方法有什么问题? (iOS)
【发布时间】:2016-03-01 02:33:04
【问题描述】:

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

想象下面的类,“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 方法就像一个单例,但每次我们调用它时,我们都会为它分配更多的内存。但它不会注册为内存泄漏。不过,在查看这段代码时,它似乎不是做单例的正确方法。

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

【问题讨论】:

  • 永远不要创建委托属性strong。应该是weak
  • 对不起,这只是一个错字。
  • 永远不要以 get 开头方法名(除非它属于一种有意义的罕见的 Objective-C 约定;这不是)。

标签: objective-c memory singleton


【解决方案1】:

使用 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;
}

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

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

HTH

【讨论】:

  • 是的,您链接到的那个是我总是如何制作单身人士的。我不确定这是否是可取的..正如你所暗示的那样,它肯定看起来不是线程安全的。是的,写这个的人?他们用它来接收 NSURLSession...facepalm...的回调...
  • 进一步阅读:这里的 ARC 行为由 "ns_consumes_self" 注释描述,隐式应用于 init。另见In ARC, why is self in init methods a consumed parameter?。 @CommaToast
  • @JoshCaswell - 确切地说,虽然我认为引用的问题略微误读了语义 - 关键是所有权从调用者转移到被调用者,只有在调用者还没有的情况下才需要调用方保留拥有所有权。在我检查当前编译器是否正确实现这些语义之前,我没有写下我的答案,以防万一......
【解决方案2】:

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

如果你想要一个以这种方式运行的单例,你必须只分配一次,并确保对+alloc 的后续调用返回相同的实例。 Peter Hosey has a very interesting blog post 关于它。

【讨论】:

  • 博客目前似乎已关闭,但代码可供阅读:bitbucket.org/boredzo/singleton/src
  • 奇怪的是,如果我这样做:Foo *foo1 = [[Foo alloc] init]; 后跟 Foo *foo2 = [[Foo alloc] init];,那么它们会返回相同的指针(即 foo1 == foo2 是真的)。那么你确信 alloc 方法确实在泄漏吗? Instruments 不会将其报告为泄漏......是否有可能新的内存页面被搁置并且永远不会被用于任何事情?它非常令人困惑。
  • 你发给我的单例链接看起来很早于 ARC。当我写单例时,我通常使用 dispatch 方法。但是这段代码来自海外……
  • 它们返回相同的指针,因为init 每次都返回foo。它在init 第一次运行时被分配,然后返回。 alloc 是负责为新实例分配内存的方法。该实例被传递到init(作为self)并且不存在对它的其他引用。如果没有 ARC,它绝对会泄漏。
  • Peter 的代码很容易更新到 ARC,但标准的 dispatch_once() 习惯用法通常没问题。我只是指向博客以进一步了解。
猜你喜欢
  • 2023-03-28
  • 1970-01-01
  • 2020-11-18
  • 2013-06-10
  • 1970-01-01
  • 2012-04-08
  • 2023-03-09
相关资源
最近更新 更多