当使用NSString的类别方法时,ARC`BAD_ACCESS`

Bes*_*esi 7 iphone cocoa-touch exc-bad-access objective-c ios

我这样称之为我的实用方法:

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 ];
Run Code Online (Sandbox Code Playgroud)

这是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;
    }
}
Run Code Online (Sandbox Code Playgroud)

在以下行中没有我必须添加一个copy声明,否则将会有一个Zombie dateString:

NSString *value = [(NSString *)(va_arg(_arguments, NSString*))copy];
Run Code Online (Sandbox Code Playgroud)

更具体地说,僵尸报告告诉我:

 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
Run Code Online (Sandbox Code Playgroud)

虽然copy声明似乎解决了这个问题,但我想了解什么不是ARC兼容代码,以便在BAD_ACCESS没有copy值字符串的情况下发生.

Nik*_*uhe 5

正如其他人所说,问题在于从可变参数列表中检索可能与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];
}
Run Code Online (Sandbox Code Playgroud)

编辑: __bridge演员告诉ARC不要对所有权做些什么.它只是期望对象存活,不转移或放弃所有权.然而key,value变量和变量在使用时保持对对象的强引用.

第二次编辑:似乎clang/ARC应该知道va_arg中的类型,并警告或只做正确的事情(例如,请参阅此内容).

我试图重现你的问题没有成功.一切都适合我:

$ clang --version
> Apple clang version 4.0 (tags/Apple/clang-421.10.48) (based on LLVM 3.1svn)
Run Code Online (Sandbox Code Playgroud)

你使用哪个Xcode版本?