Cómo crear un NSString de una cadena de formato como @ “= xxx% @, yyy =% @” y un NSArray de los objetos?

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

Pregunta

¿Hay alguna manera de crear un nuevo NSString de una cadena de formato como @ "= xxx% @, yyy =% @" y un NSArray de objetos?

En la clase NSString hay muchos métodos como:

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

pero no de ellos lleva un NSArray como un argumento, y no puedo encontrar una manera de crear un va_list de un NSArray ...

¿Fue útil?

Solución

En realidad, no es difícil crear un va_list de un NSArray. Ver excelente artículo de Matt Gallagher sobre el tema.

Esta es una categoría de NSString para hacer lo que desee:

@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

A continuación:

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

Desafortunadamente, para 64 bits, el formato va_list ha cambiado, por lo que el código anterior ya no funciona. Y probablemente no deberían utilizarse todos modos dado que depende del formato que es claramente sujetas a cambios. Dado que no hay manera muy robusta para crear un va_list, una mejor solución es simplemente para limitar el número de argumentos a un máximo razonable (digamos 10) y luego llamar stringWithFormat con los primeros 10 argumentos, algo como esto:

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

Otros consejos

Sobre la base de esta respuesta Uso de contar Referencia automática (ARC): https://stackoverflow.com/a/8217755/881197

Añadir una categoría para NSString con el siguiente método:

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

Una solución que se me vino a la mente es que podía crear un método que funciona con un gran número fijo de argumentos como:

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

También podría añadir una comprobación para ver si la cadena de formato tiene más de 21 caracteres '%' y lanzar una excepción en este caso.

@Chuck es correcta sobre el hecho de que no se puede convertir un NSArray en varargs . Sin embargo, no recomiendo buscar el patrón %@ en la cadena y su sustitución cada vez. (Sustitución de caracteres en medio de una cadena es en general bastante ineficiente, y no es una buena idea si se puede lograr lo mismo de una manera diferente.) Aquí es una manera más eficiente para crear una cadena con el formato que se está describiendo:

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

He incluido la piscina autorelease para una buena limpieza, ya que se creará una cadena autoreleased para cada entrada de la matriz y la matriz mutable está autoreleased también. Desde aquí se puede convertir esto en un método / función y volver composedString sin retener, y manejar el disparador automático en otro lugar en el código si lo desea.

Esta respuesta está libre de errores. Como se ha indicado, no hay solución a este problema que se garantiza que funcione cuando nuevas plataformas se introducen con excepción de usar el método de "10 elemento de matriz".


La respuesta por solidsun estaba funcionando bien, hasta que fui a compilar con arquitectura de 64 bits. Esto provocó un error:

EXC_BAD_ADDRESS tipo EXC_I386_GPFLT

La solución fue usar un enfoque ligeramente diferente para el paso de la lista de argumentos para el método:

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

Esto sólo funciona para matrices con un solo elemento

hay de manera general pasar una matriz a una función o un método que utiliza varargs. En este caso particular, sin embargo, usted podría fingir utilizando algo como:

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

EDIT: La respuesta aceptada afirma que existe una manera de hacer esto, pero independientemente de lo frágil que pueda parecer esta respuesta, este enfoque es mucho más frágil. Se basa en el comportamiento definido por la implementación (en concreto, la estructura de un va_list) que no está garantizado que siguen siendo los mismos. Sostengo que mi respuesta es correcta y mi solución propuesta es menos frágil, ya que sólo se basa en las características definidas de la lengua y los marcos.

Para aquellos que necesitan una solución rápida, aquí es una extensión para hacer esto en 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
    }

}

Sí, es posible. En GCC focalización de Mac OS X, por lo menos, va_list es simplemente una matriz C, por lo que va a hacer uno de id s, entonces le dice al NSArray para llenarlo:

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

Usted no debe depender de esta naturaleza en el código que debe ser portátil. los desarrolladores de iPhone, esto es una cosa que sin duda debe probar en el dispositivo.

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

Probado con formato y argumentos:

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

Resultado: xxx = XXX, YYY = yyy último componente

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

Resultado: xxx = XXX, YYY = yyy último componente

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

Resultado: XXX XXX = último componente

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

Resultado: último componente

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

Resultado: un texto

He encontrado algo de código en la web que afirma que esto es posible sin embargo no he conseguido hacerlo yo mismo, sin embargo, si usted no sabe el número de argumentos de antemano También necesita para construir la cadena de formato de forma dinámica para simplemente no veo el punto.

Es mejor que fuera sólo la construcción de la cadena por iteración la matriz.

Es posible encontrar el stringByAppendingString: o stringByAppendingFormat:. Método de instancia útil

Uno puede crear una categoría para NSString y hacer una función que recibe un formato, una matriz y devuelve la cadena con objetos reemplazados.

@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

Debido NSArray se crea en tiempo de ejecución no podemos proporcionar advertencias en tiempo de compilación, pero podemos utilizar NSAssert decirnos si el número de especificadores es igual con el número de objetos dentro de la matriz.

proyecto rel="nofollow"> en Github en esta categoría se puede encontrar. También se ha añadido la versión de Chuck mediante el uso de 'stringByReplacingCharactersInRange:' además de algunas pruebas.

Uso de un millón de objetos en serie, la versión con 'stringByReplacingCharactersInRange:' no escala muy bien (esperado aproximadamente 2 minutos luego cerró la aplicación). El uso de la versión con NSMutableString, la función hizo la cadena en unos 4 segundos. Las pruebas se realizaron utilizando simulador. Antes de su uso, se deben hacer exámenes en un dispositivo real (utilizar un dispositivo con especificaciones más bajas).

Edit: En el iPhone 5s la versión con NSMutableString toma 10.471655s (un millón de objetos); en el iPhone 5 se 21.304876s.

Aquí está la respuesta sin crear explícitamente una matriz:

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

primera cadena es la cadena de destino para ser formateado, la siguiente cadena son la cadena que se inserta en el objetivo.

No, usted no será capaz de hacerlo. llamadas de argumentos variables se resuelven en tiempo de compilación, y su NSArray tiene contenido sólo en tiempo de ejecución.

scroll top