Wie ein NSString aus einem Formatstring wie @ „xxx =% @, Yyy =% @“ und eine NSArray von Objekten zu schaffen?

StackOverflow https://stackoverflow.com/questions/1058736

Frage

Gibt es eine Möglichkeit, einen neuen zu erstellen NSString aus einem Formatstring wie @ "xxx =% @, Yyy =% @" und eine NSArray von Objekten?

In der NSString-Klasse gibt es viele Methoden wie:

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

, aber nicht von ihnen nimmt eine NSArray als Argument, und ich kann nicht einen Weg zu schaffen, eine va_list von einem NSArray ...

finden
War es hilfreich?

Lösung

Es ist eigentlich nicht schwer, einen va_list von einem NSArray zu erstellen. Siehe Matt Gallagher ausgezeichneten Artikel zu diesem Thema.

Hier ist eine NSString Kategorie zu tun, was Sie wollen:

@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

Dann:

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

Leider ist für 64-Bit hat das va_list Format geändert, so dass der obige Code nicht mehr funktioniert. Und wahrscheinlich sollte sowieso nicht verwendet werden, gegeben es auf das Format ab, die eindeutig Änderungen vorbehalten ist. Gegeben ist es keine wirklich robuste Art und Weise eine va_list, eine bessere Lösung zu schaffen, ist einfach die Anzahl der Argumente auf ein vernünftiges Maximum zu begrenzen (etwa 10) und rufen Sie dann mit string den ersten 10 Argumenten, etwa wie folgt:

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

Andere Tipps

Auf der Grundlage dieser Antwort mit Automatic Reference Counting (ARC): https://stackoverflow.com/a/8217755/881197

eine Kategorie mit dem folgende Verfahren 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;
}

Eine Lösung, die ich in dem Sinn kam, ist, dass ich eine Methode schaffen könnte, die mit einer festen großen Anzahl von Argumenten wie funktioniert:

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

Ich könnte auch einen Scheck hinzufügen, um zu sehen, ob der Format-String mehr als 21 ‚%‘ Zeichen hat und eine Ausnahme in diesem Fall werfen.

@Chuck ist richtig über die Tatsache, dass Sie keine NSArray in varargs umwandeln kann . Allerdings empfehle ich nicht für das Muster %@ im String suchen und es jedes Mal zu ersetzen. (Zeichen in der Mitte einer Zeichenfolge zu ersetzen, ist im Allgemeinen recht ineffizient und nicht eine gute Idee, wenn Sie die gleiche Sache auf eine andere Weise erreichen können.) Hier ist ein effizienter Weg, um eine Zeichenfolge mit dem Format erstellen Sie beschreiben:

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

I umfasste den Autofreigabepool für eine gute Reinigung, da ein Autoreleased String wird für jeden Array-Eintrag erstellt werden, und die änderbaren Array als auch Autoreleased. Man könnte leicht diese in eine Methode / Funktion machen und das Rück composedString ohne es zu halten, und der Griff der Autofreigabe an anderer Stelle im Code, falls gewünscht.

Diese Antwort ist fehlerhaft. Wie bereits erwähnt, gibt es für dieses Problem keine Lösung, die andere eingeführt zu arbeiten, wenn neue Plattformen gewährleistet werden als die „10-Element-Array“ -Verfahren verwendet wird.


Die Antwort von solidsun funktioniert gut, bis ich mit 64-Bit-Architektur ging zu kompilieren. Dies verursachte einen Fehler:

EXC_BAD_ADDRESS Typ EXC_I386_GPFLT

Die Lösung wurde einen etwas anderen Ansatz zu verwenden, um die Argumentliste an die Methode übergeben:

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

Dies funktioniert nur für Arrays mit einem einzelnen Elemente

Es ist kein allgemeine Weise ein Array an eine Funktion oder eine Methode zu übergeben, die varargs verwendet. In diesem speziellen Fall jedoch konnte man fälschen, indem mit so etwas wie:

for (NSString *currentReplacement in array)
    [string stringByReplacingCharactersInRange:[string rangeOfString:@"%@"] 
            withString:currentReplacement];

EDIT: Die akzeptierte Antwort behauptet, dass es eine Möglichkeit, dies zu tun ist, aber unabhängig davon, wie zerbrechlich diese Antwort könnte scheinen, dass Ansatz ist viel zerbrechlicher. Es stützt sich auf der Implementierung definiert Verhalten (insbesondere die Struktur eines va_list), die nicht garantiert ist das gleiche bleiben. Ich behaupte, daß meine Antwort ist richtig und meine vorgeschlagene Lösung ist weniger zerbrechlich, da sie nur auf definierten Merkmale der Sprache und Frameworks setzt.

Für diejenigen, die eine Swift-Lösung benötigen, hier ist eine Erweiterung dieses in Swift zu tun

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
    }

}

Ja, es ist möglich. In GCC mindestens Mac OS X, Targeting, va_list ist einfach eine C-Array, so dass Sie einen von ids machen werden, dann die NSArray sagen, es zu füllen:

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

Sie sollten sich nicht auf diese Art in Code verlassen, die tragbar sein sollte. iPhone-Entwickler, das ist eine Sache, die Sie auf jeden Fall auf dem Gerät testen.

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

Getestet mit Format und Argumenten:

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

Ergebnis: xxx = XXX, yyy = YYY letzte Komponente

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

Ergebnis: xxx = XXX, yyy = YYY letzte Komponente

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

Ergebnis: xxx = XXX letzte Komponente

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

Ergebnis: letzte Komponente

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

Ergebnis: some text

fand ich im Internet einig Code, der behauptet, dass dies möglich ist, aber ich habe mich nicht geschaffen, es zu tun, aber wenn Sie die Anzahl der Argumente im Voraus nicht wissen, müssen Sie auch dynamisch so das Format-String bauen ich sehe einfach nicht den Punkt.

Sie besser dran, nur die Zeichenfolge Gebäude durch das Array iterieren.

Sie können den stringByAppendingString finden: oder stringByAppendingFormat. Instanzmethode praktisch

Man kann eine Kategorie für NSString erstellen und eine Funktion machen, die ein Format empfängt, ein Array und gibt die Zeichenfolge mit ersetzt Objekten.

@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

Da NSArray zur Laufzeit erstellt wird, wir nicht Kompilierung-Warnungen bieten können, aber wir können NSAssert nutzen, um uns zu sagen, ob Anzahl der Spezifizierer mit Anzahl von Objekten innerhalb des Arrays entspricht.

Projekt auf Github Erstellt wo diese Kategorie zu finden ist. Chucks auch hinzugefügt Version unter Verwendung von ‚stringByReplacingCharactersInRange:‘ plus einige Tests.

Mit einer Million Objekten in Array-Version mit ‚stringByReplacingCharactersInRange:‘ skaliert nicht sehr gut (wartete ca. 2 Minuten dann die App geschlossen). Unter Verwendung der Version mit NSMutableString machte Funktion die Zeichenfolge in etwa 4 Sekunden. Die Tests wurden mit Simulator gemacht. Vor dem Gebrauch, Tests auf einem realen Gerät getan werden sollte (ein Gerät mit dem niedrigsten Spezifikationen verwenden).

Edit: Auf dem iPhone 5s die Version mit NSMutableString nimmt 10.471655s (eine Million Objekte); auf dem iPhone 5 nimmt 21.304876s.

Hier ist die Antwort, ohne dass ein Array explizit zu erstellen:

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

First String ist die Zielzeichenfolge formatiert werden, sind die nächste Zeichenfolge die Zeichenfolge im Ziel eingefügt werden.

Nein, Sie werden nicht in der Lage sein. Variable Argument Anrufe werden bei der Kompilierung gelöst und Ihre NSArray hat Inhalte nur zur Laufzeit.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top