NSString stringWithFormat 混合以允许丢失格式编号参数

发布于 2024-09-04 09:12:22 字数 2177 浏览 2 评论 0原文

基于这个几个小时前提出的问题,我决定实现一种 swizzled 方法,该方法允许我将格式化的 NSString 作为格式参数放入 stringWithFormat 中,并且在省略其中一个编号的参数引用时不会中断(%1$@, %2$@)

我已经让它工作了,但这是第一个副本,并且由于每次应用程序运行此方法可能会被调用数十万次,我需要向一些专家反馈这个问题,看看这个方法是否有任何危险信号、重大性能影响或优化

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

基本算法是:

  1. 在格式字符串中搜索 %1$@, %2$@ 变量,通过搜索 %@
  2. 如果没有找到,调用普通 stringWithFormat 并返回
  3. 循环遍历 args
  4. else,如果格式有位置变量,则 (% i$@) 对于位置 i,将 arg 添加到新的 arg 数组
  5. ,否则,不添加 arg
  6. 获取新的 arg 数组,将其转换回 va_list,然后调用initWithFormat:arguments: 获取正确的字符串。

我的想法是,我将通过此方法运行所有 [NSString stringWithFormat:] 调用。

对于许多人来说,这似乎没有必要,但是单击引用的 SO 问题(第一行)即可查看为什么我需要这样做的示例。

有想法吗?想法?更好的实施?更好的解决方案?

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. search the format string for the %1$@, %2$@ variables by searching for %@
  2. if not found, call the normal stringWithFormat and return
  3. else, loop over the args
  4. if the format has a position variable (%i$@) for position i, add the arg to the new arg array
  5. else, don't add the arg
  6. take the new arg array, convert it back into a va_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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

牵你的手,一向走下去 2024-09-11 09:12:22

哇那儿!

不要搞乱你很可能会引入微妙错误的核心方法,而是只需在项目选项中打开“静态分析器”,它就会运行每个构建 - 如果你的参数错误,它会发出编译器警告你。

我很感激您希望使应用程序更加健壮,但我认为重写此方法很可能会破坏您的应用程序而不是保存它。

Whoa there!

Instead of screwing with a core method that you very probably will introduce subtle bugs into, instead just turn on "Static Analyzer" in your project options, and it will run every build - if you get the arguments wrong it will issue a compiler warning for you.

I appreciate your desire to make the application more robust but I think it very likely that re-writing this method will more likely break your application than save it.

远昼 2024-09-11 09:12:22

定义您自己的临时方法而不是使用格式说明符和 stringWithFormat: 怎么样?例如,您可以定义自己的方法 replaceIndexPoints: 来查找 ($1) 而不是 %1$@。然后,您可以格式化字符串并独立插入翻译后的替换内容。此方法还可以采用字符串数组,在“未翻译”字符串中不存在的索引处使用 NSNull 或空字符串。

您的方法可能如下所示(如果它是 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.
}

以下是我在当前实现中看到的一些问题(一目了然):

  1. __VA_ARGS__事情在评论中解释了。
  2. 当您使用 while (arg = va_arg(args, id)) 时,您假设参数以 nil 终止(例如 arrayWithObjects: >),但对于 stringWithFormat: 这不是必需的。
  3. 我认为您不需要在 arg 循环中转义字符串格式中的 $@
  4. 我不确定如果 uaStringWithFormat: 传递的东西大于指针(即 long long 如果指针是 32 位的话),这是否会正常工作。仅当您的翻译还需要插入 long long 数量级的未本地化数字时,这才可能成为问题。

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

  1. The __VA_ARGS__ thingy explained in the comments.
  2. When you use 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.
  3. I don't think you're required to escape the $ and @ in your string format in your arg-loop.
  4. I'm not sure this would work well if 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.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文