32

@"xxx=%@, yyy=%@" のようなフォーマット文字列とオブジェクトの NSArray から新しい NSString を作成する方法はありますか?

NSSTring クラスには、次のような多くのメソッドがあります。

- (id)initWithFormat:(NSString *)format arguments:(va_list)argList
- (id)initWithFormat:(NSString *)format locale:(id)locale arguments:(va_list)argList
+ (id)stringWithFormat:(NSString *)format, ...

しかし、それらのどれも引数として NSArray を取らず、NSArray から va_list を作成する方法を見つけることができません...

4

13 に答える 13

46

実際、NSArray から va_list を作成するのは難しくありません。この件に関するMatt Gallagher の優れた記事を参照してください。

あなたが望むことを行うための NSString カテゴリは次のとおりです。

@interface NSString (NSArrayFormatExtension)

+ (id)stringWithFormat:(NSString *)format array:(NSArray*) arguments;

@end

@implementation NSString (NSArrayFormatExtension)

+ (id)stringWithFormat:(NSString *)format array:(NSArray*) arguments
{
    char *argList = (char *)malloc(sizeof(NSString *) * arguments.count);
    [arguments getObjects:(id *)argList];
    NSString* result = [[[NSString alloc] initWithFormat:format arguments:argList] autorelease];
    free(argList);
    return result;
}

@end

それで:

NSString* s = [NSString stringWithFormat:@"xxx=%@, yyy=%@" array:@[@"XXX", @"YYY"]];
NSLog( @"%@", s );

残念ながら、64 ビットでは va_list の形式が変更されたため、上記のコードは機能しなくなりました。また、明らかに変更される可能性がある形式に依存するため、とにかく使用するべきではありません。va_list を作成する確実な方法がない場合、より良い解決策は、単純に引数の数を適切な最大数 (たとえば 10) に制限してから、次のように最初の 10 個の引数で stringWithFormat を呼び出すことです。

+ (id)stringWithFormat:(NSString *)format array:(NSArray*) arguments
{
    if ( arguments.count > 10 ) {
        @throw [NSException exceptionWithName:NSRangeException reason:@"Maximum of 10 arguments allowed" userInfo:@{@"collection": arguments}];
    }
    NSArray* a = [arguments arrayByAddingObjectsFromArray:@[@"X",@"X",@"X",@"X",@"X",@"X",@"X",@"X",@"X",@"X"]];
    return [NSString stringWithFormat:format, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9] ];
}
于 2009-06-30T04:36:10.843 に答える
37

自動参照カウント (ARC) を使用したこの回答に基づく: https://stackoverflow.com/a/8217755/881197

NSString次の方法でカテゴリを追加します。

+ (id)stringWithFormat:(NSString *)format array:(NSArray *)arguments
{
    NSRange range = NSMakeRange(0, [arguments count]);
    NSMutableData *data = [NSMutableData dataWithLength:sizeof(id) * [arguments count]];
    [arguments getObjects:(__unsafe_unretained id *)data.mutableBytes range:range];
    NSString *result = [[NSString alloc] initWithFormat:format arguments:data.mutableBytes];
    return result;
}
于 2012-11-21T18:28:16.390 に答える
16

私の頭に浮かんだ解決策の1つは、次のような固定された多数の引数で機能するメソッドを作成できることです。

+ (NSString *) stringWithFormat: (NSString *) format arguments: (NSArray *) arguments {
    return [NSString stringWithFormat: format ,
          (arguments.count>0) ? [arguments objectAtIndex: 0]: nil,
          (arguments.count>1) ? [arguments objectAtIndex: 1]: nil,
          (arguments.count>2) ? [arguments objectAtIndex: 2]: nil,
          ...
          (arguments.count>20) ? [arguments objectAtIndex: 20]: nil];
}

また、フォーマット文字列に21'%'文字を超えるかどうかを確認するチェックを追加し、その場合は例外をスローすることもできます。

于 2009-06-29T22:28:00.977 に答える
4

@Chuckは、 NSArray を varargs に変換できないという事実について正しいです。%@ただし、文字列内のパターンを検索して毎回置き換えることはお勧めしません。(文字列の途中で文字を置き換えるのは、一般的に非常に非効率的であり、別の方法で同じことを達成できる場合はお勧めできません。) 説明している形式で文字列を作成するより効率的な方法を次に示します。

NSArray *array = ...
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:[array count]];
for (id object in array) {
    [newArray addObject:[NSString stringWithFormat:@"x=%@", [object description]]];
}
NSString *composedString = [[newArray componentsJoinedByString:@", "] retain];
[pool drain];

自動解放された文字列が配列エントリごとに作成され、可変配列も自動解放されるため、適切なハウスキーピングのために自動解放プールを含めました。これを簡単にメソッド/関数にcomposedStringして、それを保持せずに返し、必要に応じてコードの他の場所で自動解放を処理できます。

于 2009-06-29T16:37:32.903 に答える
4

この答えはバグです。前述のように、「10 要素配列」メソッドを使用する以外に、新しいプラットフォームが導入されたときに機能することが保証されているこの問題の解決策はありません。


64ビットアーキテクチャでコンパイルするまで、solidsunによる答えはうまくいきました。これによりエラーが発生しました:

EXC_BAD_ADDRESS タイプ EXC_I386_GPFLT

解決策は、引数リストをメソッドに渡すために少し異なるアプローチを使用することでした。

+ (id)stringWithFormat:(NSString *)format array:(NSArray*) arguments;
{
     __unsafe_unretained id  * argList = (__unsafe_unretained id  *) calloc(1UL, sizeof(id) * arguments.count);
    for (NSInteger i = 0; i < arguments.count; i++) {
        argList[i] = arguments[i];
    }

    NSString* result = [[NSString alloc] initWithFormat:format, *argList] ;//  arguments:(void *) argList];
    free (argList);
    return result;
}

これは、単一の要素を持つ配列に対してのみ機能します

于 2014-01-28T23:59:46.023 に答える
3

Swift ソリューションが必要な人のために、Swift でこれを行うための拡張機能を次に示します。

extension String {

    static func stringWithFormat(format: String, argumentsArray: Array<AnyObject>) -> String {
        let arguments = argumentsArray.map { $0 as! CVarArgType }
        let result = String(format:format, arguments:arguments)
        return result
    }

}
于 2015-08-30T08:11:38.047 に答える
2

はい、可能です。Mac OS X をターゲットとする GCC では、少なくとも、va_list単純な C 配列であるため、ids の 1 つを作成し、NSArray にそれを埋めるように指示します。

NSArray *argsArray = [[NSProcessInfo processInfo] arguments];
va_list args = malloc(sizeof(id) * [argsArray count]);
NSAssert1(args != nil, @"Couldn't allocate array for %u arguments", [argsArray count]);

[argsArray getObjects:(id *)args];

//Example: NSLogv is the version of NSLog that takes a va_list instead of separate arguments.
NSString *formatSpecifier = @"\n%@";
NSString *format = [@"Arguments:" stringByAppendingString:[formatSpecifier stringByPaddingToLength:[argsArray count] * 3U withString:formatSpecifier startingAtIndex:0U]];
NSLogv(format, args);

free(args);

移植可能であるべきコードでは、この性質に頼るべきではありません。iPhone 開発者の皆さん、これは必ずデバイスでテストする必要があることの 1 つです。

于 2009-06-30T03:35:35.207 に答える
1
- (NSString *)stringWithFormat:(NSString *)format andArguments:(NSArray *)arguments {
    NSMutableString *result = [NSMutableString new];
    NSArray *components = format ? [format componentsSeparatedByString:@"%@"] : @[@""];
    NSUInteger argumentsCount = [arguments count];
    NSUInteger componentsCount = [components count] - 1;
    NSUInteger iterationCount = argumentsCount < componentsCount ? argumentsCount : componentsCount;
    for (NSUInteger i = 0; i < iterationCount; i++) {
        [result appendFormat:@"%@%@", components[i], arguments[i]];
    }
    [result appendString:[components lastObject]];
    return iterationCount == 0 ? [result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] : result;
}

フォーマットと引数でテスト済み:

NSString *format = @"xxx=%@, yyy=%@ last component";
NSArray *arguments = @[@"XXX", @"YYY", @"ZZZ"];

結果: xxx=XXX、yyy=YYY 最後​​のコンポーネント

NSString *format = @"xxx=%@, yyy=%@ last component";
NSArray *arguments = @[@"XXX", @"YYY"];

結果: xxx=XXX、yyy=YYY 最後​​のコンポーネント

NSString *format = @"xxx=%@, yyy=%@ last component";
NSArray *arguments = @[@"XXX"];

結果: xxx=XXX 最後のコンポーネント

NSString *format = @"xxx=%@, yyy=%@ last component";
NSArray *arguments = @[];

結果: 最後のコンポーネント

NSString *format = @"some text";
NSArray *arguments = @[@"XXX", @"YYY", @"ZZZ"];

結果: テキスト

于 2016-11-16T17:26:13.313 に答える
0

NSString のカテゴリを作成し、フォーマット、配列を受け取り、置き換えられたオブジェクトで文字列を返す関数を作成できます。

@interface NSString (NSArrayFormat)

+ (NSString *)stringWithFormat:(NSString *)format arrayArguments:(NSArray *)arrayArguments;

@end

@implementation NSString (NSArrayFormat)

+ (NSString *)stringWithFormat:(NSString *)format arrayArguments:(NSArray *)arrayArguments {
    static NSString *objectSpecifier = @"%@"; // static is redundant because compiler will optimize this string to have same address
    NSMutableString *string = [[NSMutableString alloc] init]; // here we'll create the string
    NSRange searchRange = NSMakeRange(0, [format length]);
    NSRange rangeOfPlaceholder = NSMakeRange(NSNotFound, 0); // variables are declared here because they're needed for NSAsserts
    NSUInteger index;
    for (index = 0; index < [arrayArguments count]; ++index) {
        rangeOfPlaceholder = [format rangeOfString:objectSpecifier options:0 range:searchRange]; // find next object specifier
        if (rangeOfPlaceholder.location != NSNotFound) { // if we found one
            NSRange substringRange = NSMakeRange(searchRange.location, rangeOfPlaceholder.location - searchRange.location);
            NSString *formatSubstring = [format substringWithRange:substringRange];
            [string appendString:formatSubstring]; // copy the format from previous specifier up to this one
            NSObject *object = [arrayArguments objectAtIndex:index];
            NSString *objectDescription = [object description]; // convert object into string
            [string appendString:objectDescription];
            searchRange.location = rangeOfPlaceholder.location + [objectSpecifier length]; // update the search range in order to minimize search
            searchRange.length = [format length] - searchRange.location;
        } else {
            break;
        }
    }
    if (rangeOfPlaceholder.location != NSNotFound) { // we need to check if format still specifiers
        rangeOfPlaceholder = [format rangeOfString:@"%@" options:0 range:searchRange];
    }
    NSAssert(rangeOfPlaceholder.location == NSNotFound, @"arrayArguments doesn't have enough objects to fill specified format");
    NSAssert(index == [arrayArguments count], @"Objects starting with index %lu from arrayArguments have been ignored because there aren't enough object specifiers!", index);
    return string;
}

@end

NSArray は実行時に作成されるため、コンパイル時に警告を出すことはできませんが、NSAssert を使用して、指定子の数が配列内のオブジェクトの数と等しいかどうかを知ることができます。

このカテゴリを見つけることができる Github でプロジェクトを作成しました。また、「stringByReplaceingCharactersInRange:」といくつかのテストを使用して、Chuck のバージョンを追加しました。

配列に 100 万個のオブジェクトを使用すると、「stringByReplaceingCharactersInRange:」を含むバージョンはうまくスケーリングされません (約 2 分待ってからアプリを閉じました)。NSMutableString を使用したバージョンを使用すると、関数は約 4 秒で文字列を作成しました。テストはシミュレーターを使用して行われました。使用する前に、実際のデバイスでテストを行う必要があります (最低スペックのデバイスを使用してください)。

編集: iPhone 5s では、NSMutableString を含むバージョンは 10.471655 秒 (100 万オブジェクト) かかります。iPhone 5 では 21.304876 秒かかります。

于 2015-06-20T10:18:52.420 に答える
0

これが可能であると主張するコードをウェブ上で見つけましたが、自分でそれを行うことはできませんでしたが、事前に引数の数がわからない場合は、フォーマット文字列を動的に構築する必要があるため、ポイントがわかりません。

配列を反復して文字列を作成する方がよいでしょう。

stringByAppendingString: または stringByAppendingFormat: インスタンス メソッドが便利な場合があります。

于 2009-06-29T15:31:12.223 に答える
-2

配列を明示的に作成しない場合の答えは次のとおりです。

   NSString *formattedString = [NSString stringWithFormat:@"%@ World, Nice %@", @"Hello", @"Day"];

最初の文字列はフォーマットされるターゲット文字列で、次の文字列はターゲットに挿入される文字列です。

于 2013-06-26T04:53:07.047 に答える
-3

いいえ、できません。可変引数の呼び出しはコンパイル時に解決され、NSArray は実行時にのみ内容を持ちます。

于 2009-06-29T15:39:14.423 に答える