2012-07-07 43 views
1

好吧,我是一名經驗豐富的C++開發人員。我正在嘗試構建一個相當可觀的Cocoa應用程序時即時學習Objective-C。在爲這個項目做準備時,我已經用Cocoa做了一些更簡單的應用程序,並且我認爲我可以很好地處理大多數的概念。分析器抱怨多線程可可應用程序中可能的資源泄漏

的內存管理模式還是有點含糊,我可是讓我建立一個與內存分析,以幫助我找到問題向右走,並說實話它一直真棒,卻更多地幫助我瞭解的Objective-C內存管理的任何文檔。

所以這是我的問題。

我有兩個線程使用performSelector:onThread:withObject:waitUntilDone:方法相互通信。它們通過傳入的方法傳遞給對象,方法參數爲withObject:

樣本代碼如下:

- (BOOL)postMessage:(id <MessageTarget>)sender messageId:(NSInteger)msgId messageData:(void*)data 
{ 
    // message is allocated and retained here. retain count will be +2 
    Message* message = [[[Message alloc] initWithSender:sender messageId:msgId messageData:data] retain]; 
    if(message) 
    { 
     // message will be released in other thread. 
     [self performSelectorOnMainThread:@selector(messageHandler:) withObject:message waitUntilDone:NO]; 
     // message is released here and retain count will be +1 or 0 depending on thread ordering 
     [message release]; 
     return YES; 
    } 
    return NO; 
} 

- (BOOL)sendMessage:(id <MessageTarget>)sender messageId:(NSInteger)msgId messageData:(void*)data messageResult:(void**)result 
{ 
    // message is allocated and retained here. retain count will be +2 
    Message* message = [[[Message alloc] initWithSender:sender messageId:msgId messageData:data] retain]; 
    if(message) 
    { 
     // message will be released in other thread. retain count will be +1 on return 
     [self performSelectorOnMainThread:@selector(messageHandler:) withObject:message waitUntilDone:YES]; 
     if(result) 
      *result = [message result]; 
     // message is released here and retain count will be 0 triggering deallocation 
     [message release]; 
     return YES; 
    } 
    return NO; 
} 

- (void)messageHandler:(Message*)message 
{ 
    // message will have a retain count of +1 or +2 in here based on thread execution order 
    if(message) 
    { 
     switch ([message messageId]) 
     { 
      case 
       ... 
      break; 

      default: 
       ... 
      break; 
     } 
     // message is released here bringing retain count to +1 or 0 depending on thread execution ordering 
     [message release]; 
    } 
} 

分析器抱怨在postMessage:sendMessage:分配的Message對象的可能的泄漏,但該對象在messageHandler:釋放。代碼運行正常,不會泄漏,我懷疑分析器根本無法看到Message對象正在被釋放到另一個線程中。

現在,如果你想知道爲什麼我做的第二件在後保留/發送方法,而不是在messageHandler:方法,這是因爲postMessage:方法,就是要在後方法異步和[message release]之前可以得到執行messageHandler:中的[message retain]會使我失去一個無效的對象。如果我在sendMessage:方法的情況下這樣做,它會工作得很好,因爲它是同步的。

那麼有沒有更好的方式來做到這一點,滿足記憶分析儀?或者,也許有一種方法可以給內存分析器提示該對象實際上正在釋放?

更新:

托裏下面提供的答案,但我想澄清什麼,我必須做什麼,從他建議不同。

他建議我messageHandler:方法中的屬性如下

- (void)messageHandler:(Message*) __attribute__((ns_consumed)) message; 

,因爲對象被傳遞到performSelector:和分析器沒有看到它沿傳遞到messageHandler:

這也不太工作

由於performSelector:調用由Cocoa定義,而不是由我定義,因此我無法將該屬性添加到它。

解決這個問題的辦法是調用換到performSelector:如下:

- (void)myPerformSelector:(SEL)sel onThread:(NSThread*)thread withObject:(id) __attribute__((ns_consumed)) message waitUntilDone:(BOOL)wait; 
{ 
    [self performSelector:sel onThread:thread withObject:message waitUntilDone:wait]; 
} 

然後就可以調用包裝函數,分析儀將看到屬性,而不是抱怨不平衡保留/釋放對。 最後,我不喜歡額外的間接方法來擺脫警告,所以我使用了預處理器,正如我在下面的評論中所解釋的。但是我可以看到使用這種方法可能有用的情況。

+0

我發現我可以用'的#ifndef __clang_analyzer__'包分配代碼抑制警告。這確實擺脫了警告,但我認爲壓制警告是一種破解,因此是最後的手段。 – 2012-07-07 23:37:18

回答

2

你做不是需要在使用這個API時明確地將引用計數操作轉移到輔助線程。

  • a)您的調用者持有時的參考waitUntilFinished是真實的,
  • b)和後面+ [NSThread performSelector:…的實施還沒有(見:- [NSRunLoop performSelector:target:argument:order:modes:])。

在這種情況下,不需要跨線程傳遞參數計數任務(或self)。

它將立即執行,或者self,並且在另一個線程的運行循環隊列中(並且在對象執行選擇器之後釋放該自身並且釋放該參數)時將保留參數(它是objc類型的)。

(不使用REF-OP __attribute__ s至關鎖)

+0

用我寫的代碼如上所示,我的'messageHandler:'方法中我的retain count是2,因爲我期望它在[[NSObject performSelector:onThread:withObject:waitUntilDone:]'方法中沒有插入的保留。我期望它與你所描述的一致。 – 2012-07-09 07:16:08

+0

寫了一個測試應用程序,並確認你說的是什麼。我在第一次測試中被拋棄了,因爲我沒有控制何時發佈發佈,並且在發佈其中一個發佈之後正在查看保留數。我希望他們會澄清,在文檔中,因爲我們中的一些新手尚不知道可可的內部運作。謝謝Justin。 – 2012-07-09 07:51:06

+0

它在抽象層中非常深,但您可以從這類方法中得到一些東西(除非參數不是objc類型或VA列表)。根本不是一個noob問題,imo!不客氣。 – justin 2012-07-09 08:11:33

0

您應該能夠通過審慎使用Clang的ns_consumed屬性使分析儀開心。正如你所建議的那樣,這給了內存分析器一個提示,即在完成函數調用後釋放消息將被髮送到參數。你會使用它想:

- (void)messageHandler:(Message*) __attribute__((ns_consumed)) message 

有關於在Clang analyzer documentation可可內存管理註釋的更多信息。您可能想要將該屬性設置包裝在NS_COSUMED宏中,以便與該頁面上建議的其他編譯器兼容。

+0

這不起作用。 'messageHandler:'方法使用的'__attribute __((ns_consumed))'不起作用,因爲分析器無法通過調用'performSelector:'來追蹤。然而,通過圍繞'performSelector:'構建一個包裝器方法,使用該屬性,我能夠讓分析器變得安靜。例子:' - (void)myPerformSelector:(SEL)sel onThread:(NSThread *)thread withObject:(id)__attribute __((ns_consumed))object waitUntilDone:(BOOL)wait'我不喜歡額外的間接方向,我正在用'#ifndef __clang_analyzer__'包裝分配並稱它爲好。 – 2012-07-09 04:11:19

+0

雖然我仍然打電話回答。謝謝您的幫助。 – 2012-07-09 04:14:14