FOUNDATION_EXPORT void NSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
- (instancetype)initWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2);
+ (instancetype)arrayWithObjects:(id)firstObj, ... NS_REQUIRES_NIL_TERMINATION; // 注意区别
在c/c++中使用可变参数参考《C/C++ 使用可变参数 & 原理》,在objective-c中使用情况也差不多,一些宏定义说明如下:
----------------------------------------------------------------------------------------------------------------------------------
NS_FORMAT_FUNCTION(1,2)的意思:
// NSObjCRuntime.h
#if !defined(NS_FORMAT_FUNCTION)
#if (__GNUC__*10+__GNUC_MINOR__ >= 42) && (TARGET_OS_MAC || TARGET_OS_EMBEDDED)
#define NS_FORMAT_FUNCTION(F,A) __attribute__((format(__NSString__, F, A)))
#else
#define NS_FORMAT_FUNCTION(F,A)
#endif
#endif
__attribute__,是GNU编译器的一个特性,这个宏是一个编译器指令,我们在代码中通过定义这个东西,可以inform编译器我们代码 的一些逻辑,从而在编译器避免一些错误,在运行期提高性能。__attribute__在很多代码中都有应用,非常实用。
__attribute__ format ,这个东西能告知编译器,我们的代码在处理printf,scanf这样变参数的函数的时候,哪个参数是format string,哪个参数是参数列表,这样可以避免代码中的一些问题,比如:
/* like printf() but to standard error only */
extern void eprintf(const char *format, ...)
__attribute__((format(printf, 1, 2))); /* 1=format 2=params */
/* printf only if debugging is at the desired level */
extern void dprintf(int dlevel, const char *format, ...)
__attribute__((format(printf, 2, 3))); /* 2=format 3=params */
从上面可以看出,我们定义了eprintf函数,第一个参数是Format String,第二个参数是对应Format String的参数列表,下面的dprintf也是一样,这样一来,编译器就能检测出下面这样的代码错误:
1 extern void eprintf(const char *format, ...)
2 __attribute__((format(printf, 1, 2)));
3
4 void foo()
5 {
6 eprintf("s=%s\n", 5); /* error on this line */
7
8 eprintf("n=%d,%d,%d\n", 1, 2); /* error on this line */
9 }
$ cc -Wall -c test.c
test.c: In function `foo':
test.c:6: warning: format argument is not a pointer (arg 2)
test.c:8: warning: too few arguments for format
其他一些
__attribute__特性:
__attribute__ const, 这个东西能告诉编译器,
在给定参数的情况下,这个function始终返回同样的值。这样可以帮助程序提高性能,比如:
extern int square(int n) __attribute__((const));
...
for (i = 0; i < 100; i++ )
{
total += square(5) + i;
}
如果我们在square函数中没有定义__attribute__ const的话,在下面的那个循环中,程序每次都要产生一个调用square函数的代码。但是这里指定了const之后,程序就知道,对于同一个输入参数 5,返回值都是一样的。
这样程序就会执行一次square,然后cache这个函数的return value,这样下次循环开始,对square函数的调用就没有函数调用的逻辑了,直接返回上次的结果
----------------------------------------------------------------------------------------------------------------------------------
在objective-c中使用可变参数的例子:
- (void)foo:(NSString *)format, ...
{
va_list args;
va_start(args, format);
NSString *str = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
printf([str UTF8String]);
[str release];
}
- (IBAction) doo: (UIButton*) sender
{
//须留意不定参函数的调用格式,逗号分隔的序列,应该它们整体是作为函数的一个参数传入
[self foo : @"My name %@, %@", @"Unmi", @"Yes"];
}
- (void)method:(NSString *)value,...
{
//指向变参的指针
va_list list;
//使用第一个参数来初使化list指针
va_start(list, value);
while (YES)
{
//返回可变参数,va_arg第二个参数为可变参数类型,如果有多个可变参数,依次调用可获取各个参数
NSString *string = va_arg(list, NSString*);
if (!string) {
break;
}
NSLog(@"%@",string);
}
//结束可变参数的获取
va_end(list);
}
函数调用:[self method:@"1",@"2",@"3",nil];像大多数变参函数一样,未尾一定要加上nil,因为这一组宏都没有提供对参数个数的检测,当然你可以会问为何NSLog的参数中我们都不用在末尾添加nil的参数呢,那是因为NSLog的第一个参数是一个格式化字符串,通过这个字条串就能获得后面的参数个数,所以如果你的函数还能有其它的参数能够显式的指出变参个数,当然你也可以书写(但在函数体中需要修改为按已知个数调用va_arg),仍然推荐以上的写法
@interface sqlHelper : NSObject
{
}
-(int) executeInsertWithSql:(NSString *) statement, ...;
@end
.m文件
-(int) executeInsertWithSql:(NSString *) statement, ...
{
PLSqliteDatabase* dbPointer = [SqliteDataBase setUp];
argsArray = [[NSMutableArray alloc] init];
id arg;
va_list argList;
if(statement)
{
va_start(argList,statement);
while (arg = va_arg(argList,id))
{
[argsArray addObject:arg];
}
va_end(argList);
}
BOOL bResult = [dbPointer executeUpdate:statement,[argsArray objectAtIndex:0],[argsArray objectAtIndex:1]];
return bResult;
}
// 在调用的时候要在参数结尾的时候加 nil
sqlHelper *sqlCom = [[sqlHelper alloc] init];
[sqlCom executeInsertWithSql:@"INSERT INTO authorInfo(author,age) VALUES (?,?)",@"cheungching",@"25", nil];
-(void)vaMethod:(id)object1, ...{
va_list argList;
id arg;
if (object1) {
va_start(argList, object1);
while ((arg = va_arg(argList, id))) {
NSLog(@"%@",arg);
}
va_end(argList);
}
}
// 调用
[self vaMethod:someObj,button,@"ss",nil];
注意第一个参数为object1,之后才是可变参数列表。
Disscussion:
--1.不定参数可以指定任何实际的类型,(id) 可真是任何类型了;
--2.Objective-C 的不定参数,即 ... 也必须放在函数的最后面,如还有其他参数时,foo 要写成:
- (void)fooState: (BOOL) enable withFormat: (NSString *)format, ...
- (void)fooFormat: (NSString *)format, ... withState: (BOOL) enable // 错的
--3.在调用的时候要不要在参数结尾的时候加 nil,回想下 [NSMutableArray arrayWithObjects: 1, 2, 3, nil] 这个构造过程,最后一个 nil 能让 va_arg 取参数时碰到 nil 则断定为 NO,终止循环。为何像 NSLog 调用不需要最后一个 nil?Because -stringWithFormat: and NSLog can infer the number of arguments based on their format strings (the first argument). -arrayWithObjects: can't.
总结:在objective-c中使用可变参数格式为:(id)object1, ... 一般情况下object1为NSString类型,然后在方法内部使用va_start、va_arg等获取参数。至于调用的时候加不加nil,依赖于方法的实现,如果方法中以参数是否为nil作为结束条件(arg = va_arg(argList, id)为nil则va_end),则调用时必须加nil结尾。另外方法后可加一些宏定义(例如NS_FORMAT_FUNCTION(1,2))确定方法的一些格式,编译的时候对代码加以验证。
------------------------------------------------------------------------------------------------------------------
参考:
1.http://unmi.cc/obejctive-c-var-arguments/ 《Obejctive-C 中定义可变参函数》
2.http://mobile.51cto.com/iphone-280106.htm 《Objective-C可变参数函数定义》
3.http://www.cnblogs.com/super119/archive/2011/04/05/2005592.html 《Using GNU C __attribute__ 阅读笔记》