7

我这样称呼我的实用方法:

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

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没有copyfor 值字符串的情况下发生。

4

2 回答 2

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];
}

编辑: __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 版本?

于 2012-07-20T13:01:44.753 回答
2

这是clang中的一个错误。

它应该工作

ARC与可变参数列表兼容。如果不是,你会从编译器中得到一个错误。

该变量value是一个强引用,而结果va_arg(_arguments, NSString *)是一个不安全的未保留引用:您可以编写va_arg(_arguments, __unsafed_unretained NSString *)并获得完全相同的编译程序集,但尝试使用另一个所有权限定符会导致编译器错误,因为它不受支持。

因此,当存储值value并假设变量被实际使用时,编译器应该发出一个调用,并在变量被破坏时objc_retain与调用平衡。objc_release根据该报告,第二个调用被发出,而第一个调用丢失。

这会导致由(并且只有这个)返回的字符串崩溃,stringWithDate:因为它是唯一不是常量的字符串。所有其他参数都是编译器生成的常量字符串,它们简单地忽略任何内存管理方法,因为只要加载了可执行文件,它们就会一直存在于内存中。

因此,我们需要了解为什么编译器会发出没有相应保留的发布。由于您不执行任何手动内存管理,也不通过使用__bridge_transferor强制转换来欺骗所有权规则__bridge_retained,我们可以假设问题来自编译器。

未定义的行为不是原因

有两个原因可能导致编译器发出无效的程序集。您的代码要么包含未定义的行为,要么编译器中存在错误。

当您的代码尝试执行 C 标准未定义的某些事情时,就会发生未定义的行为:当编译器遇到未定义的行为时,它有权为所欲为。未定义的行为会导致程序可能会或可能不会崩溃。大多数情况下,问题与未定义行为发生在同一位置,但有时它可能看起来不相关,因为编译器希望未定义行为不会发生并依赖该预期来执行一些优化。

因此,让我们看看您的代码是否包含未定义的行为。是的,因为该方法replaceTokensWithStrings:可以调用va_start并返回而不va_end被调用(return nilfor循环内)。C 标准明确声明(在第 7.15.1.3 节中)这样做是未定义的行为。

如果我们替换return nilbreak,您的代码现在有效。然而这并不能解决问题。

怪编译器

现在我们排除了所有其他可能的原因,我们需要面对现实。clang 中有一个错误。我们可以通过执行许多产生有效编译程序集的细微更改来看到这一点:

  • 如果我们用-O0而不是编译-Os,它可以工作。
  • 如果我们用 clang 4.1 编译,它可以工作。
  • 如果我们在条件之前向对象发送消息if,它就会起作用。
  • 如果我们用 替换va_arg(_arguments, NSString *)va_arg(_arguments, id)它可以工作。我建议您使用此解决方法。
于 2012-07-23T10:11:37.783 回答