簡體   English   中英

當使用NSString的類別方法時,ARC`BAD_ACCESS`

[英]ARC `BAD_ACCESS` when using category method of NSString

我這樣稱之為我的實用方法:

NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:@"dd.MM.yy HH:mm"];
NSString *dateString = [dateFormat stringFromDate:[NSDate date]];

return [[Environment sharedInstance].versionLabelFormat replaceTokensWithStrings:
     @"VERSION", APP_VERSION, 
     @"BUILD", APP_BULD_NUMBER, 
     @"DATETIME" , dateString, 
     nil ];

這是NSString類別方法

-(NSString *)replaceTokensWithStrings:(NSString *)firstKey, ... NS_REQUIRES_NIL_TERMINATION{

    NSString *result = self;

        va_list _arguments;
        va_start(_arguments, firstKey);

        for (NSString *key = firstKey; key != nil; key = va_arg(_arguments, NSString*)) {

            // The value has to be copied to prevent crashes
            NSString *value = [(NSString *)(va_arg(_arguments, NSString*))copy];

            if(!value){
                // Every key has to have a value pair otherwise the replacement is invalid and nil is returned

                NSLog(@"Premature occurence of nil. Each token must be accompanied by a value: %@", result);
                return nil;
            }

            result = [result replaceToken:key withString:value];
        }
        va_end(_arguments);

    // Check if there are any tokens which were not yet replaced (for example if one value was nil)

    if([result rangeOfString:@"{"].location == NSNotFound){
        return result;
    } else {
        NSLog(@"Failed to replace tokens failed string still contains tokens: %@", result);
        return nil;
    }
}

在以下行中沒有我必須添加一個copy語句,否則會有一個帶有dateString的Zombie:

NSString *value = [(NSString *)(va_arg(_arguments, NSString*))copy];

更具體地說, 僵屍報告告訴我:

 1 Malloc       NSDateFormatter stringForObjectValue:
   Autorelease  NSDateFormatter stringForObjectValue:
 2 CFRetain     MyClass versionString:
 3 CFRetain     replaceToken:withString:
 2 CFRelease    replaceToken:withString:
 1 CFRelease    replaceTokensWithStrings:   ( One release too much!)
 0 CFRelease    MyClass versionString:
-1 Zombie       GSEventRunModal

雖然copy語句似乎解決了這個問題,但我想了解什么不是ARC兼容代碼,這樣BAD_ACCESS就會在沒有值字符串的copy情況下發生。

正如其他人所說,問題在於從可變參數列表中檢索可能與ARC不兼容的對象的方式。

va_arg有一種有趣的方法,可以返回ARC可能不知道的特定類型的值。 我不確定這是否是clang中的錯誤,或者它是否是ARC的預期行為。 我將澄清這個問題並相應地更新帖子。

作為一種解決方法,只需通過在參數處理中使用void指針來避免問題,並以ARC安全的方式將它們正確地轉換為對象:

for (NSString *key = firstKey; key != nil; key = (__bridge NSString *)va_arg(_arguments, void *)) {
    NSString *value = (__bridge NSString *)va_arg(_arguments, void *);
    NSAssert(value != NULL, @"Premature occurence of nil.");
    result = [result stringByReplacingToken:key
                                 withString:value];
}

編輯: __bridge演員告訴ARC不要對所有權做些什么。 它只是期望對象存活,不轉移或放棄所有權。 然而, keyvalue變量在使用時保持對對象的強引用。

第二次編輯:似乎clang / ARC應該知道va_arg中的類型,並警告或只做正確的事情( 例如,請參閱此內容 )。

我試圖重現你的問題沒有成功。 一切都適合我:

$ clang --version
> Apple clang version 4.0 (tags/Apple/clang-421.10.48) (based on LLVM 3.1svn)

你使用哪個Xcode版本?

這是鏗鏘的錯誤。

它應該工作

ARC與可變參數列表兼容 如果不是,您將從編譯器中收到錯誤。

變量value是一個強引用,而va_arg(_arguments, NSString *)是一個不安全的未返回引用:您可以編寫va_arg(_arguments, __unsafed_unretained NSString *)並獲得完全相同的編譯程序集但嘗試使用另一個所有權限定符將因為它不受支持而得到編譯器錯誤。

所以,存儲在所述值時value並假設該變量被實際使用時,編譯器應該發出一個呼叫到objc_retain和與呼叫平衡它objc_release當變量被破壞。 根據該報告,第二個呼叫被發出,而第一個呼叫丟失。

這會導致stringWithDate:返回的字符串崩潰stringWithDate:只有這一個),因為它是唯一不是常量的字符串。 所有其他參數都是由編譯器生成的常量字符串,它只是忽略任何內存管理方法,因為只要加載了可執行文件,它們就會持久存儲在內存中。

因此,我們需要理解為什么編譯器發出沒有相應保留的版本。 由於您不執行任何手動內存管理並且不通過使用__bridge_transfer__bridge_retained進行轉換來欺騙所有權規則,因此我們可以假設問題來自編譯器。

未定義的行為不是原因

有兩個原因可能導致編譯器發出無效的程序集。 您的代碼包含未定義的行為,或者編譯器中存在錯誤。

未定義行為當編譯器遇到一個未定義的行為,它有權為所欲為 :當你的代碼試圖執行不是由C標准定義的東西出現。 未定義的行為會導致可能會或可能不會崩潰的程序。 大多數情況下,問題出現在與未定義行為相同的位置,但有時可能看起來不相關,因為編譯器期望未定義的行為不會發生,並依賴於期望來執行某些優化。

因此,讓我們看看您的代碼是否包含未定義的行為。 是的,因為方法replaceTokensWithStrings:可以調用va_start並返回而不調用va_end (在for循環中return nil )。 C標准明確指出(在第7.15.1.3節中)這樣做是未定義的行為。

如果我們用break替換return nil ,那么你的代碼現在是有效的。 然而,這並沒有解決問題。

責備編譯器

現在我們已經消除了所有其他可能的原因,我們需要面對現實。 clang中有一個bug。 我們可以通過執行許多產生有效編譯匯編的細微更改來看到這一點:

  • 如果我們使用-O0而不是-Os ,則可以正常工作。
  • 如果我們用clang 4.1編譯,它就可以了。
  • 如果我們在if條件之前向對象發送消息,它就可以工作。
  • 如果我們用va_arg(_arguments, id)替換va_arg(_arguments, NSString *) va_arg(_arguments, id) ,它就可以了。 我建議你使用這個解決方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM