2013-01-13 31 views
2

我對ARC,橋接和某些非免費橋接CF對象仍有點困惑。我目前的困惑在於CFSocket。我很確定我正在清理,但分析工具告訴我,否則。也許我的不和諧讓我看不到漏洞,或者工具是錯誤的。我還沒有準備好指責這個工具,所以我正在尋找其他的眼睛來指出這個問題。例如,我是否缺少一種__bridge將所有權轉讓給我?爲什麼這被標記爲使用ARC的潛在泄漏?

在我的項目中,使用ARC,我有一個基於TCP的服務器。我們稱這個類爲「MyServer」。在MyServer中,我有一個內部屬性套接字,定義如下:

@property (assign) CFSocketRef socket; 

此屬性包含服務器運行時的套接字引用。停止服務器將釋放引用,同時刪除服務器對象。我也試圖清理在啓動服務器的過程中創建的任何可能的泄漏。正是在這個領域,我對靜態分析有一個問題。

服務器開始使用此方法:

​​

createSocket方法創建一個套接字(杜),像這樣:

-(BOOL)createSocket 
{ 
    BOOL result = YES; 

    self.socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, 
           IPPROTO_TCP, 0, NULL, NULL); 
    if (self.socket != NULL) { 
     int reuse = true; 
     int fileDescriptor = CFSocketGetNative(self.socket); 
     if (setsockopt(fileDescriptor, SOL_SOCKET, SO_REUSEADDR, 
         (void *)&reuse, sizeof(int)) == 0) { 

      struct sockaddr_in address; 
      memset(&address, 0, sizeof(address)); 
      address.sin_len = sizeof(address); 
      address.sin_family = AF_INET; 
      address.sin_addr.s_addr = htonl(INADDR_ANY); 
      address.sin_port = htons(self.port); 


      CFDataRef addressData = CFDataCreate(NULL, 
               (const UInt8 *)&address, 
               sizeof(address)); 

      if (addressData && CFSocketSetAddress(self.socket, addressData) == kCFSocketSuccess) { 
       self.listenHandle = [[NSFileHandle alloc] initWithFileDescriptor:fileDescriptor 
                    closeOnDealloc:YES]; 

       [[NSNotificationCenter defaultCenter] addObserver:self 
                 selector:@selector(receiveIncomingConnectionNotification:) 
                  name:NSFileHandleConnectionAcceptedNotification 
                  object:nil]; 
       [self.listenHandle acceptConnectionInBackgroundAndNotify]; 

       _state = SERVER_STATE_RUNNING; 
      } else { 
       result = NO; 
       [self errorWithName:@"Unable to bind socket to address."]; 
      } 
      CFRelease(addressData); 
     } else { 
      [self errorWithName:@"Unable to set socket options."]; 
      CFRelease(self.socket); 
      CFSocketInvalidate(self.socket); 
      CFRelease(self.socket); 
      self.socket = nil; 
      result = NO; 
     } 
    } else { 
     [self errorWithName:@"Unable to create socket."]; 
     // CFRelease(self.socket); //NO - CFRelease(NULL) is a runtime error! 
     result = NO; 
    } 

    return result; 
} 

當我在此代碼運行靜態分析,Xcode的報告self.socket周圍有一堆潛在的泄漏。這裏有一個例子,從上面的createSocket方法:

(static analysis error)

這的確是真的,我不引用對象的任何更多的這條道路。也許有一些方法可以告訴系統我想擁有這個對象,並且它抱怨的原因是它不能告訴我這麼做。我是否應該使用__bridge中的一個來表達這些信息?我試圖讓財產保留或強大,但不建立,因爲它不是一個對象。任何其他想法?

+0

我注意到其他一些執行路徑,我們沒有正確清理,所以我已經調整了相應的邏輯,以便加倍確保我們正確清理。 – Rob

回答

2

這裏的問題是,您正在使用一個屬性,這是混淆編譯器。 (它不知道通話self.socket = ...,其實[self setSocket:...]期望一個已經保留的項目,並將其存儲。

如果您直接使用實例變量,它應該安靜的警告,因爲它會明白你(在這種情況下,由於你的財產是私人的,並且可以分配,所以不妨使用實例變量)

另外,你正在使用套接字之後你調用CFRelease(你在CFRelease之後調用CFSocketInvalidate),這是一個壞主意。

+0

+1我是一個習慣的生物。在我的習慣中有以下兩種:我避開實例變量並專門使用屬性,我傾向於儘可能地通過'self.'構造使用屬性方法。因爲套接字是一種非常簡單的「存儲我」類型的屬性,沒有外部訪問,也沒有任何「有趣」的相關行爲,所以我想我會將它變成一個ivar,並直接按照您的建議使用它。我還會驗證任何失效和釋放操作的順序,以確保這些操作是正常的。 –

+0

我正在做這個答案,因爲它最直接地回答我的問題。儘管如此,我認爲這個答案和羅布的答案都是有價值的,但是我必須選擇不超過一個才能得到複選標記。 –

1

它被報告爲潛在的泄漏,因爲它是潛在的泄漏。如果createSocket方法被調用了兩次,則會發生泄漏,因爲您永遠不會釋放套接字。

嘗試增加:

CFRelease(self.socket); 

只是調用CFSocketCreate之前。並確保您在dealloc方法中的套接字上調用CFRelease

+0

我不認爲這是分析儀可以推斷的。 –

+0

但是這是一個潛在的泄漏,是的。 –

+0

這是一個公正的評論 - 如果startServer在服務器已經運行的時候被調用,它應該檢查它是否確實在運行,以及套接字是否已經存在。至於分析器是否可以推斷出這種行爲,我並不擔心 - 我更關心泄漏,所以謝謝。 +1。 –

3

一對夫婦的想法:

  1. 問題,傑西指出,是你正在使用的存取方法socket和分析越來越有點糊塗了,以爲對象傳遞給setSocket方法泄漏,沒有意識到您將它保存在實例變量中。如果您將self.socket替換爲_socket,那麼與self.socket相關的警告將消失。

  2. 你的代碼生成第二個警告有關addressData涉及到的事實,你有一個執行路徑,從而addressData可能是NULL,但你還是打電話CFRelease。在嘗試使用CFRelease之前,您應該明確檢查代碼是否爲NULL

  3. 你在釋放你的套接字兩次之前,一次在無效之前,以及之後再次釋放。顯然,你不希望兩次釋放。我還建議將套接字設置爲NULL,而不是nil,並不是說它很重要。

  4. 部分原因是因爲我的改變點#2(其中我,否則需要添加另一個else條款,如果addressDataNULL),但相關的也作爲一個邏輯普遍的轉變,因爲你有很多相關的執行路徑失敗,但只有一個與成功有關,我建議默認resultNO並將其設置爲YES在那個單一的成功執行路徑。這確保我們在創建成功的所有不同路徑中使套接字無效並釋放套接字,但是收聽不成功。我相信有一些執行路徑以前沒有被正確覆蓋。

因此,我結束了這個引渡createSocket

-(BOOL)createSocket 
{ 
    BOOL result = NO; 

    _socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, 
           IPPROTO_TCP, 0, NULL, NULL); 

    if (_socket != NULL) { 
     int reuse = true; 
     int fileDescriptor = CFSocketGetNative(self.socket); 
     if (setsockopt(fileDescriptor, SOL_SOCKET, SO_REUSEADDR, 
         (void *)&reuse, sizeof(int)) == 0) { 

      struct sockaddr_in address; 
      memset(&address, 0, sizeof(address)); 
      address.sin_len = sizeof(address); 
      address.sin_family = AF_INET; 
      address.sin_addr.s_addr = htonl(INADDR_ANY); 
      address.sin_port = htons(self.port); 


      CFDataRef addressData = CFDataCreate(NULL, 
               (const UInt8 *)&address, 
               sizeof(address)); 

      if (addressData) { 
       if (CFSocketSetAddress(_socket, addressData) == kCFSocketSuccess) { 
        self.listenHandle = [[NSFileHandle alloc] initWithFileDescriptor:fileDescriptor 
                     closeOnDealloc:YES]; 

        [[NSNotificationCenter defaultCenter] addObserver:self 
                  selector:@selector(receiveIncomingConnectionNotification:) 
                   name:NSFileHandleConnectionAcceptedNotification 
                   object:nil]; 
        [self.listenHandle acceptConnectionInBackgroundAndNotify]; 

        result = YES; 
        _state = SERVER_STATE_RUNNING; 
       } else { 
        [self errorWithName:@"Unable to bind socket to address."]; 
       } 
       CFRelease(addressData); 
      } 
     } 

     if (result != YES) { 
      [self errorWithName:@"Unable to set socket options."]; 
      CFSocketInvalidate(_socket); 
      CFRelease(_socket); 
      _socket = NULL; 
     } 
    } else { 
     [self errorWithName:@"Unable to create socket."]; 
    } 

    return result; 
} 

我原來的答案是專注於核心基礎內存管理的基礎知識,而重要的是,不立即有關手頭的問題。

原來的答案:

是,如果一個Core Foundation的函數調用的名稱有CreateCopy,你自己的對象。因此,您必須:

很明顯,前者適用於此,但總的來說,任何一種方法都可行。

+0

我不認爲他想在這裏發佈CF;他希望它留在self.socket中。 –

+0

@JesseRusak完全正確。我對標準核心基礎內存管理建議太快了。我相應地更新了我的答案。 – Rob

+0

嘿,挺。從我+1。 –