如何检查 NSString 格式是否包含与可变参数相同数量的说明符



为了确保NSString initWithFormat:arguments:返回的格式化字符串符合预期,我需要确定是否存在与参数相同数量的格式说明符。下面是一个(略微做作且高度编辑)示例:

- (void)thingsForStuff:(CustomStuff)stuff, ...
{
    NSString *format;
    switch (stuff)
    {
        case CustomStuffTwo:
            format = @"Two things: %@ and %@";
        break;
        case CustomStuffThree:
            format = @"Three things: %@, %@, and %@";
        break;
        default:
            format = @"Just one thing: %@";
        break;
    }
    va_list args;
    va_start(args, method);
    // Want to check if format has the same number of %@s as there are args, but not sure how
    NSString *formattedStuff = [[NSString alloc] initWithFormat:format arguments:args];
    va_end(args);
    NSLog(@"Things: %@", formattedStuff);
}

使用此方法,[self thingsForStuff:CustomStuffTwo, @"Hello", @"World"]将记录

"两件事:你好和世界"

。但[self thingsForStuff:CustomStuffTwo, @"Hello"]会记录

"两件事:你好和"

。希望在它发生之前被抓住的东西。

有没有办法计算字符串中的格式说明符,最好是轻量级/便宜的东西?

好吧,我创建了自己的正则表达式,我不知道它是否会捕获所有这些,并且可能会最终发现一些误报,但似乎对我有用:

static NSString *const kStringFormatSpecifiers =
@"%(?:\d+\$)?[+-]?(?:[lh]{0,2})(?:[qLztj])?(?:[ 0]|'.{1})?\d*(?:\.\d+)?[@dDiuUxXoOfeEgGcCsSpaAFn]";

您可以使用以下方法计算参数的数量:

NSRegularExpression *regEx = [NSRegularExpression regularExpressionWithPattern: kStringFormatSpecifiers options:0 error:nil];
NSInteger numSpecifiers = [regEx numberOfMatchesInString: yourString options:0 range:NSMakeRange(0, yourString.length)];

有没有办法计算字符串中的格式说明符,最好是 轻量级/便宜的东西?

不,真的不是。 至少,如果您希望它适用于所有可能的格式字符串,则不会。 您必须复制 stringWithFormat: 使用的解析器。 即不要试图验证所有内容。

你可以数%的数量,但这不会捕捉到%%或其他特殊情况。 这可能足以满足您的目的。

由于 C

和 Objective-C 处理像您这样的可变参数函数/方法的方式,您通常无法判断用户提供了多少参数。

以下是处理您的情况的两种方法。

首先,寻找另一种方法来做到这一点。 传递给该方法的参数数在编译时确定。 因此,也许您应该只有三种方法,而不是使用可变参数方法:

- (void)doStuff:(CustomStuff)stuff withThing:(Thing *)thing;
- (void)doStuff:(CustomStuff)stuff withThing:(Thing *)thing1 thing:(Thing *)thing2;
- (void)doStuff:(CustomStuff)stuff withThing:(Thing *)thing1 thing:(Thing *)thing2 hatWearer:(Cat *)cat;

并且,您可以根据要传递的参数数选择要在编译时调用的正确方法,从而完全消除 switch 语句。

其次,我看到您预定义的格式字符串仅使用%@格式。 这是否意味着您希望用户仅将对象传递给您的方法(除了 (CustomStuff)stuff 参数)?

如果用户只将对象传递给您的方法,并且您要求这些参数为非 nil,那么您可以让编译器来帮助您。 更改方法以要求用户在参数列表的末尾传递nil。 你可以告诉编译器,参数列表必须通过声明方法(在你的@interface中)来终止,如下所示:

@interface MyObject : NSObject
- (void)thingsForStuff:(CustomStuff)stuff, ... NS_REQUIRES_NIL_TERMINATION
@end

现在,编译器将警告用户"方法调度中缺少哨兵",如果他调用您的方法而不在参数列表末尾放置文字 nil。

因此,将 API 更改为需要一些非 nil 参数后跟 nil 参数后,您可以更改方法以计算非 nil 参数,如下所示:

- (void)thingsForStuff:(CustomStuff)stuff, ... {
    int argCount = 0;
    va_list args;
    va_start(args, stuff);
    while (va_arg(args, id)) {
        ++argCount;
    }
    va_end(args)
    int expectedArgCount;
    NSString *format;
    switch (stuff) {
        case CustomStuffTwo:
            expectedArgCount = 2;
            format = @"Two things: %@ and %@";
            break;
        case CustomStuffThree:
            expectedArgCount = 3;
            format = @"Three things: %@, %@, and %@";
            break;
        // etc.
    }
    NSAssert(argCount == expectedArgCount, @"%@ %s called with %d non-nil arguments, but I expected %d", self, (char*)_cmd, argCount, expectedArgCount);
    va_start(args, stuff);
    NSString *formattedStuff = [[NSString alloc] initWithFormat:format arguments:args];
    va_end(args);
    NSLog(@"Things: %@", formattedString);
}

您可以计算格式说明符的数量,但 IIRC 您将永远无法计算传递到变量参数方法中的参数数量。这是因为 C 在堆栈上推送参数的方式没有指定它推送了多少参数。

大多数函数通过要求最后一个参数为 nil 或某种终止符来克服这个问题(参见 [NSArray arrayWithObjects:])。甚至还有一个宏允许编译器检查这一点并在编译时发出警告。

您可以在

函数原型的末尾使用NS_FORMAT_FUNCTION,就像stringWithFormat NSString方法一样。

所以你的方法的原型应该是这样的:

- (void)thingsForStuff:(CustomStuff)stuff, ... NS_FORMAT_FUNCTION(1,2);

long specifierCount = [myFormatString componentsSepardByString:@"%"].count;

这会让你接近。这只是一个简单的分裂。 您必须考虑转义的 % 值。

最新更新