NSString stringWithFormatで引数の位置/インデックスを指定する方法はありますか?


82

C#には、文字列形式指定子で引数インデックスを指定できる構文があります。例:

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

引数は複数回使用でき、提供された引数は使用しないようにすることもできます。別の質問では、C / C ++の同じフォーマットが%[index]$[format]たとえばの形式で言及されています%1$i。NSStringにこの構文を完全に尊重させることはできませんでした。これは、フォーマットから引数省略した場合に適切に動作するためです。以下は期待どおりに機能しません(ageパラメーターをNSObject *として逆参照しようとするため、EXC_BAD_ACCESS )。

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

これらの呼び出しはすべて、OSXで正しく機能します。

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

Objective-C / CocoaでNSStringを使用してこれを実現する方法はありますか?ローカリゼーションの目的に役立ちます。


バグレポートを提出してください(そしてバグ番号をお知らせください)。
Dirk Theisen

回答:


125

NSStringとCFStringは、並べ替え可能/位置引数をサポートします。

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

また、Appleのドキュメントを参照してください:文字列リソース


5
いくつかの説明を付けて質問を更新しました。Cocoaは、フォーマットから省略された引数を尊重していないようです。これは、私が受け取っていたアクセス違反の副作用でした。
ジェイソン

2
Cの可変引数の動作方法のため、省略された引数を尊重することはできません。引数の数やサイズを知るための標準的な方法はありません。文字列解析は、フォーマット指定子から情報を推測することによってこれを処理します。これには、指定子が実際に存在する必要があります。
Jens Ayton

1
va_argsがどのように機能するかを理解しています。ただし、これは期待どおりに機能しているようです。printf( "Age:%2 $ i"、[name UTF8String]、age); 引数を並べ替えたり欠落させたりして他のprintfを試しましたが、すべて期待どおりの出力が得られますが、NSStringでは得られません。
ジェイソン

7
繰り返しになりますが、:stringWithFormat:は、すべての引数がフォーマット文字列で指定されている限り、位置引数をサポートします。
ジェイソン

1
また、文字列形式
指定子

1

次のコードは、この問題で指定されたバグを修正します。これは回避策であり、ギャップを埋めるためにプレースホルダーの番号を付け直します。

+ (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;
}

0

さらに調査を行った後、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にこれが実装されていれば便利です。


1
sprintfはCocoaの一部ではなく、C標準ライブラリの一部であり、その実装はstringWithFormat:/initWithFormat:です。
Peter Hosey

以前のコメントを明確にする:CocoaバージョンはstringWithFormat:/initWithFormat:です。これは、(現在、CFStringCreateWithFormatsprintfおよび友人の実装とは別の実装です。
Peter Hosey

4
正確に512バイトでmsgを初期化することは、ランダムオブジェクトに対してランダムセレクターを実行するのとほぼ同じくらい安全であるという事実をコメントすることはほとんど役に立たないと思いますが、とにかく。知らない人へ:固定サイズのバッファは、起動する最も簡単な方法のいくつかです。グーグル:バッファオーバーフロー
ジョージペン。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.