为了确保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:
使用的解析器。 即不要试图验证所有内容。
你可以数%
的数量,但这不会捕捉到%%
或其他特殊情况。 这可能足以满足您的目的。
和 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;
这会让你接近。这只是一个简单的分裂。 您必须考虑转义的 % 值。