Based on this SO question asked a few hours ago, I have decided to implement a swizzled method that will allow me to take a formatted NSString
as the format arg into stringWithFormat
, and have it not break when omitting one of the numbered arg references (%1$@, %2$@
)
I have it working, but this is the first copy, and seeing as this method is going to be potentially called hundreds of thousands of times per app run, I need to bounce this off of some experts to see if this method has any red flags, major performance hits, or optimizations
#define NUMARGS(...) (sizeof((int[]){__VA_ARGS__})/sizeof(int))
@implementation NSString (UAFormatOmissions)
+ (id)uaStringWithFormat:(NSString *)format, ... {
if (format != nil) {
va_list args;
va_start(args, format);
// $@ is an ordered variable (%1$@, %2$@...)
if ([format rangeOfString:@"$@"].location == NSNotFound) {
//call apples method
NSString *s = [[[NSString alloc] initWithFormat:format arguments:args] autorelease];
va_end(args);
return s;
}
NSMutableArray *newArgs = [NSMutableArray arrayWithCapacity:NUMARGS(args)];
id arg = nil;
int i = 1;
while (arg = va_arg(args, id)) {
NSString *f = [NSString stringWithFormat:@"%%%d\$\@", i];
i++;
if ([format rangeOfString:f].location == NSNotFound) continue;
else [newArgs addObject:arg];
}
va_end(args);
char *newArgList = (char *)malloc(sizeof(id) * [newArgs count]);
[newArgs getObjects:(id *)newArgList];
NSString* result = [[[NSString alloc] initWithFormat:format arguments:newArgList] autorelease];
free(newArgList);
return result;
}
return nil;
}
The basic algorithm is:
%1$@
, %2$@
variables by searching for %@
%i$@
) for position i, add the arg to the new arg arrayva_list
, and call initWithFormat:arguments:
to get the correct string.The idea is that I would run all [NSString stringWithFormat:]
calls through this method instead.
This might seem unnecessary to many, but click on to the referenced SO question (first line) to see examples of why I need to do this.
Ideas? Thoughts? Better implementations? Better Solutions?
How about defining your own interim method instead of using format specifiers and stringWithFormat:
? For example, you could define your own method replaceIndexPoints:
to look for ($1)
instead of %1$@
. You would then format your string and insert translated replacements independently. This method could also take an array of strings, with NSNull
or empty strings at the indexes that don't exist in the “untranslated” string.
Your method could look like this (if it were a category method for NSMutableString
):
- (void) replaceIndexPointsWithStrings:(NSArray *) replacements
{
// 1. look for largest index in "self".
// 2. loop from the beginning to the largest index, replacing each
// index with corresponding string from replacements array.
}
Here's a few issues that I see with your current implementation (at a glance):
__VA_ARGS__
thingy explained in the comments.while (arg = va_arg(args, id))
, you are assuming that the arguments are nil
terminated (such as for arrayWithObjects:
), but with stringWithFormat:
this is not a requirement.$
and @
in your string format in your arg-loop.uaStringWithFormat:
was passed something larger than a pointer (i.e. long long
if pointers are 32-bit). This may only be an issue if your translations also require inserting unlocalised numbers of long long
magnitude.