objective-c 如何从像@"xxx=%@, yyy=%@" 这样的格式字符串和对象的 NSArray 创建一个 NSString?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1058736/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 21:48:19  来源:igfitidea点击:

How to create a NSString from a format string like @"xxx=%@, yyy=%@" and a NSArray of objects?

objective-ccocoansstringnsarraystring-formatting

提问by Panagiotis Korros

Is there any way to create a new NSString from a format string like @"xxx=%@, yyy=%@" and a NSArray of objects?

有什么方法可以从格式字符串(如@"xxx=%@, yyy=%@" 和对象的 NSArray 中创建新的 NSString?

In the NSSTring class there are many methods like:

在 NSSTring 类中有许多方法,例如:

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

but non of them takes a NSArray as an argument, and I cannot find a way to create a va_list from a NSArray...

但是他们都没有将 NSArray 作为参数,而且我找不到从 NSArray 创建 va_list 的方法...

回答by Peter N Lewis

It is actually not hard to create a va_list from an NSArray. See Matt Gallagher's excellent articleon the subject.

从 NSArray 创建 va_list 实际上并不难。请参阅 Matt Gallagher关于该主题的优秀文章

Here is an NSString category to do what you want:

这是一个 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

Then:

然后:

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

Unfortunately, for 64-bit, the va_list format has changed, so the above code no longer works. And probably should not be used anyway given it depends on the format that is clearly subject to change. Given there is no really robust way to create a va_list, a better solution is to simply limit the number of arguments to a reasonable maximum (say 10) and then call stringWithFormat with the first 10 arguments, something like this:

不幸的是,对于 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] ];
}

回答by SolidSun

Based on this answer using Automatic Reference Counting (ARC): https://stackoverflow.com/a/8217755/881197

基于使用自动引用计数 (ARC) 的这个答案:https: //stackoverflow.com/a/8217755/881197

Add a category to NSStringwith the following method:

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

回答by Panagiotis Korros

One solution that came to my mind is that I could create a method that works with a fixed large number of arguments like:

我想到的一个解决方案是,我可以创建一个方法来处理固定的大量参数,例如:

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

I could also add a check to see if the format string has more than 21 '%' characters and throw an exception in that case.

我还可以添加检查以查看格式字符串是否包含超过 21 个 '%' 字符并在这种情况下抛出异常。

回答by Quinn Taylor

@Chuckis correct about the fact that you can't convert an NSArray into varargs. However, I don't recommend searching for the pattern %@in the string and replacing it each time. (Replacing characters in the middle of a string is generally quite inefficient, and not a good idea if you can accomplish the same thing in a different way.) Here is a more efficient way to create a string with the format you're describing:

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

I included the autorelease pool for good housekeeping, since an autoreleased string will be created for each array entry, and the mutable array is autoreleased as well. You could easily make this into a method/function and return composedStringwithout retaining it, and handle the autorelease elsewhere in the code if desired.

我包含了自动释放池以进行良好的内务管理,因为将为每个数组条目创建一个自动释放的字符串,并且可变数组也会自动释放。你可以很容易地把它变成一个方法/函数并返回composedString而不保留它,如果需要的话,可以在代码的其他地方处理自动释放。

回答by Brett

This answer is buggy. As noted, there is no solution to this problem that is guaranteed to work when new platforms are introduced other than using the "10 element array" method.

这个答案是错误的。如前所述,除了使用“10 元素数组”方法之外,没有保证在引入新平台时可以解决此问题的解决方案。



The answer by solidsun was working well, until I went to compile with 64-bit architecture. This caused an error:

solidsun 的答案运行良好,直到我使用 64 位架构进行编译。这导致了一个错误:

EXC_BAD_ADDRESS type EXC_I386_GPFLT

EXC_BAD_ADDRESS 类型 EXC_I386_GPFLT

The solution was to use a slightly different approach for passing the argument list to the method:

解决方案是使用稍微不同的方法将参数列表传递给方法:

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

This only works for arrays with a single element

这仅适用于具有单个元素的数组

回答by Bocaxica

For those who need a Swift solution, here is an extension to do this in Swift

对于那些需要 Swift 解决方案的人,这里有一个扩展可以在 Swift 中执行此操作

extension String {

    static func stringWithFormat(format: String, argumentsArray: Array<AnyObject>) -> String {
        let arguments = argumentsArray.map { 
for (NSString *currentReplacement in array)
    [string stringByReplacingCharactersInRange:[string rangeOfString:@"%@"] 
            withString:currentReplacement];
as! CVarArgType } let result = String(format:format, arguments:arguments) return result } }

回答by Chuck

There is no general wayto pass an array to a function or method that uses varargs. In this particular case, however, you could fake it by using something like:

没有一般的方式来传递数组的函数或方法,它使用可变参数。但是,在这种特殊情况下,您可以使用以下内容来伪造它:

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

EDIT: The accepted answer claims there is a way to do this, but regardless of how fragile this answer might seem, that approach is far more fragile. It relies on implementation-defined behavior (specifically, the structure of a va_list) that is not guaranteed to remain the same. I maintain that my answer is correct and my proposed solution is less fragile since it only relies on defined features of the language and frameworks.

编辑:接受的答案声称有一种方法可以做到这一点,但无论这个答案看起来多么脆弱,这种方法都要脆弱得多。它依赖于va_list不能保证保持不变的实现定义的行为(特别是 a 的结构)。我坚持认为我的答案是正确的,而且我提出的解决方案不那么脆弱,因为它只依赖于语言和框架的定义特性。

回答by Peter Hosey

Yes, it is possible. In GCC targeting Mac OS X, at least, va_listis simply a C array, so you'll make one of ids, then tell the NSArray to fill it:

对的,这是可能的。在面向 Mac OS X 的 GCC 中,至少,va_list只是一个 C 数组,因此您将创建ids 之一,然后告诉 NSArray 填充它:

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

You shouldn't rely on this nature in code that should be portable. iPhone developers, this is one thing you should definitely test on the device.

您不应该在应该可移植的代码中依赖这种性质。iPhone 开发人员,这是您绝对应该在设备上测试的一件事。

回答by Ruslan Soldatenko

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

Tested with format and arguments:

使用格式和参数进行测试:

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

Result: xxx=XXX, yyy=YYY last component

结果:xxx=XXX, yyy=YYY 最后一个组件

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

Result: xxx=XXX, yyy=YYY last component

结果:xxx=XXX, yyy=YYY 最后一个组件

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

Result: xxx=XXX last component

结果:xxx=XXX 最后一个组件

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

Result: last component

结果:最后一个组件

@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

Result: some text

结果:一些文本

回答by Athan

One can create a category for NSString and make a function which receives a format, an array and returns the string with replaced objects.

可以为 NSString 创建一个类别并创建一个函数,该函数接收格式、数组并返回带有替换对象的字符串。

##代码##

Because NSArray is created at runtime we cannot provide compile-time warnings, but we can use NSAssert to tell us if number of specifiers is equal with number of objects within array.

因为 NSArray 是在运行时创建的,所以我们不能提供编译时警告,但是我们可以使用 NSAssert 来告诉我们说明符的数量是否与数组中的对象数量相等。

Created a projecton Github where this category can be found. Also added Chuck's version by using 'stringByReplacingCharactersInRange:' plus some tests.

在 Github 上创建了一个项目,可以在其中找到此类别。还通过使用 'stringByReplacingCharactersInRange:' 加上一些测试添加了 Chuck 的版本。

Using one million objects into array, version with 'stringByReplacingCharactersInRange:' doesn't scale very well (waited about 2 minutes then closed the app). Using the version with NSMutableString, function made the string in about 4 seconds. The tests were made using simulator. Before usage, tests should be done on a real device (use a device with lowest specs).

在数组中使用一百万个对象,带有“stringByReplacingCharactersInRange:”的版本不能很好地扩展(等了大约 2 分钟然后关闭了应用程序)。使用带有 NSMutableString 的版本,函数在大约 4 秒内生成字符串。测试是使用模拟器进行的。使用前,应在真实设备上进行测试(使用最低规格的设备)。

Edit: On iPhone 5s the version with NSMutableString takes 10.471655s (one million objects); on iPhone 5 takes 21.304876s.

编辑:在 iPhone 5s 上,带有 NSMutableString 的版本需要 10.471655s(一百万个对象);在 iPhone 5 上需要 21.304876s。