【问题标题】:Is there a way to specify argument position/index in NSString stringWithFormat?有没有办法在 NSString stringWithFormat 中指定参数位置/索引?
【发布时间】:2009-06-30 13:58:46
【问题描述】:

C# 的语法允许您在字符串格式说明符中指定参数索引,例如:

string message = string.Format("Hello, {0}. You are {1} years old. How does it feel to be {1}?", name, age);

您可以多次使用参数,也可以省略使用提供的参数。 Another question 以 %[index]$[format] 的形式提到了 C/C++ 的相同格式,例如%1$i。我无法让 NSString 完全 遵守此语法,因为在格式中省略参数 时它确实表现良好。以下内容无法按预期工作(EXC_BAD_ACCESS,因为它试图将 age 参数取消引用为 NSObject*):

int age = 23;
NSString * name = @"Joe";
NSString * message = [NSString stringWithFormat:@"Age: %2$i", name, age];

仅当格式中没有缺少参数时才尊重位置参数(这似乎是一个奇怪的要求):

NSString * message = [NSString stringWithFormat:@"Age: %2$i; Name: %1$@", name, age];

所有这些调用在 OS X 中都能正常工作:

printf("Age: %2$i", [name UTF8String], age);
printf("Age: %2$i %1$s", [name UTF8String], age);

有没有办法在 Objective-C / Cocoa 中使用 NSString 来完成这个?这对于本地化目的很有用。

【问题讨论】:

  • 提交错误报告(并让我们知道错误#)。

标签: objective-c cocoa


【解决方案1】:

NSString 和 CFString 支持可重新排序/位置参数。

NSString *string = [NSString stringWithFormat: @"Second arg: %2$@, First arg %1$@", @"<1111>", @"<22222>"];
NSLog(@"String = %@", string);

另外,请参阅Apple: String Resources 上的文档

【讨论】:

  • 我已经更新了这个问题并进行了一些澄清。看来 Cocoa 不尊重格式中省略的参数,这是我收到的访问冲突的副作用。
  • 尊重省略的参数是不可能的,因为 C 中可变参数的工作方式。没有标准的方法可以知道参数的数量或它们的大小。字符串解析通过从格式说明符中推断信息来处理这个问题,这要求说明符确实存在。
  • 我了解 va_args 的工作原理;但是,这似乎按预期工作: printf("Age: %2$i", [name UTF8String], age);我已经尝试了其他带有重新排序/丢失参数的 printf,它们都给出了预期的输出,而 NSString 没有。
  • 重申我的发现,然后:stringWithFormat: 支持位置参数,只要所有参数都在格式字符串中指定。
【解决方案2】:

以下代码修复了此问题中指定的错误。这是一种解决方法,可以重新编号占位符以填补空白。

+ (id)stringWithFormat:(NSString *)format arguments:(NSArray*) arguments 
{
    NSMutableArray *filteredArguments = [[NSMutableArray alloc] initWithCapacity:arguments.count];
    NSMutableString *correctedFormat = [[NSMutableString alloc ] initWithString:format];
    NSString *placeHolderFormat = @"%%%d$";

    int actualPlaceholderIndex = 1;

    for (int i = 1; i <= arguments.count; ++i) {
        NSString *placeHolder = [[NSString alloc] initWithFormat:placeHolderFormat, i];
        if ([format rangeOfString:placeHolder].location != NSNotFound) {
            [filteredArguments addObject:[arguments objectAtIndex:i - 1]];

            if (actualPlaceholderIndex != i) {
                NSString *replacementPlaceHolder = [[NSString alloc] initWithFormat:placeHolderFormat, actualPlaceholderIndex];
                [correctedFormat replaceAllOccurrencesOfString:placeHolder withString:replacementPlaceHolder];    
                [replacementPlaceHolder release];
            }
            actualPlaceholderIndex++;
        }
        [placeHolder release];
    }

    if (filteredArguments.count == 0) {
        //No numbered arguments found: just copy the original arguments. Mixing of unnumbered and numbered arguments is not supported.
        [filteredArguments setArray:arguments];
    }

    NSString* result;
    if (filteredArguments.count == 0) {
        //Still no arguments: don't use initWithFormat in this case because it will crash: just return the format string
        result = [NSString stringWithString:format];
    } else {
        char *argList = (char *)malloc(sizeof(NSString *) * [filteredArguments count]);
        [filteredArguments getObjects:(id *)argList];
        result = [[[NSString alloc] initWithFormat:correctedFormat arguments:argList] autorelease];
        free(argList);    
    }

    [filteredArguments release];
    [correctedFormat release];

    return result;
}

【讨论】:

    【解决方案3】:

    经过更多研究,Cocoa 似乎尊重printf 中的位置语法。因此,另一种模式是:

    char msg[512] = {0};
    NSString * format = @"Age %2$i, Name: %1$s"; // loaded from resource in practice
    sprintf(msg, [format UTF8String], [name UTF8String], age);
    NSString * message = [NSString stringWithCString:msg encoding:NSUTF8StringEncoding];
    

    不过,如果在 NSString 上有一个实现就好了。

    【讨论】:

    • sprintf 不是 Cocoa 的一部分,它是 C 标准库的一部分,其实现是 stringWithFormat:/initWithFormat:。
    • 澄清我之前的评论:Cocoa 版本是stringWithFormat:/initWithFormat:。这是与sprintf 和朋友们的单独实现(目前为CFStringCreateWithFormat)。
    • 我想几乎没有必要评论这样一个事实,即用 512 个字节初始化 msg 与在随机对象上执行随机选择器一样安全,但无论如何。对于任何不知道的人:固定大小的缓冲区是一些最容易被解雇的方法。谷歌:缓冲区溢出
    猜你喜欢
    • 1970-01-01
    • 2022-12-01
    • 2013-12-17
    • 2015-06-02
    • 2021-12-27
    • 1970-01-01
    • 2012-08-21
    • 1970-01-01
    • 2021-12-09
    相关资源
    最近更新 更多