2016-03-01 39 views
1

我想了解下面的代碼有什麼問題。這個init方法有什麼問題會導致內存問題? (iOS)

想象一下下面的類,「富」:

@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 

我們在代碼中調用美孚從什麼地方是這樣的:

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

然後我們得到的hereTakeThisFooBarDic方法回調後。

但問題是我們正在獲得無限的內存增長。看來Foo的init方法就像一個singleton,但每次我們稱它爲它分配更多的內存。它雖然沒有註冊爲內存泄漏。儘管如此,在查看這段代碼時,它看起來並不像做單身人士的正確方式。

我想知道這段代碼的作者做錯了什麼。

+0

永遠不要使委託屬性'strong'。它應該是'弱'。 – rmaddy

+0

對不起,這只是一個錯字。 – CommaToast

+1

永遠不要使用'get'開始一個方法名(除非它屬於各種罕見的Objective-C慣例之一,在這個慣例中它是有道理的,但這不是)。 – bbum

回答

2

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

+0

是的,你鏈接到的是我如何總是讓我的單身。我不確定這個其他是否可行。正如你所提到的那樣,它肯定不是線程安全的。是的,這是誰寫的?他們使用它來接收NSURLSession的回調... facepalm ... – CommaToast

+0

進一步閱讀:ARC的行爲在這裏描述[[ns_consumes_self]](http://clang.llvm.org/docs/AutomaticReferenceCounting.html#消費參數)註釋,隱式應用於'init'。另請參見[在ARC中,爲什麼init方法中的self是一個消耗參數?](http://stackoverflow.com/q/17242328)。 @CommaToast –

+0

@JoshCaswell - 確切地說,儘管我認爲引用的問題稍微誤讀了語義 - 關鍵是所有權從調用者轉移到被調用者,只有在調用者尚未擁有所有權時才需要調用者方保留。我沒有寫我的答案,直到我檢查了當前的編譯器正確地實現這些語義,以防萬一...... – CRD

1

每當您撥打[[Foo alloc] init]時,都會創建一個新的Foo,但會被忽略。如果你不是用ARC編譯的話,這實際上是一個泄漏:你已經分配了一些東西而不是釋放它。如果你是,它仍然是奇怪的,我不確定如果它仍然是一個泄漏,它會讓我感到驚訝。

如果你想要一個以這種方式運行的單例,你只能分配一次,並確保後續調用+alloc返回同一個實例。關於它,請參考Peter Hosey has a very interesting blog post

+0

博客目前似乎處於關閉狀態,但代碼可供查看:https://bitbucket.org/boredzo/singleton/src –

+0

奇怪的是,如果我這樣做:'Foo * foo1 = [[Foo alloc ];然後是'Foo * foo2 = [[Foo alloc] init];'然後它們返回相同的指針(即'foo1 == foo2'爲true)。那麼你是否確信alloc方法確實泄漏?儀器不會將其報告爲泄漏......是否有可能將一個新的內存頁面放在一邊,並且永遠不會用於任何事情?它非常混亂。 – CommaToast

+0

另外,你發給我的單身鏈接看起來非常棒。當我編寫單例時,我通常使用調度方法。但是這個代碼來自海外...... – CommaToast