在此处踩过坑,故留记一下。
先看段代码:
- (BOOL)execute_insert_sql:(NSString *)sql,...NS_REQUIRES_NIL_TERMINATION{
if (![self isOpenDB]) {
if (self.dbPath.length) {
BOOL open = [self openDBWithPath:self.dbPath];
if (!open)
return NO;
}
return NO;
}
__block BOOL success;
va_list args;
va_start(args, sql);
id eachObject;
NSMutableArray* arr = [[NSMutableArray alloc] init];
while ((eachObject = va_arg(args, id))) {
[arr addObject:eachObject];
}
[self.dbQueue inDatabase:^(FMDatabase *db) {
success = [db executeUpdate:sql withArgumentsInArray:arr];
}];
va_end(args);
NSLog(@"execute_insert_sql:%@ fmdb_success:%d",sql,success);
return success;
}
概念说明
NS_REQUIRES_NIL_TERMINATION
- 源码如下:
#if !defined(NS_REQUIRES_NIL_TERMINATION)
#if TARGET_OS_WIN32
#define NS_REQUIRES_NIL_TERMINATION
#else
#if defined(__APPLE_CC__) && (__APPLE_CC__ >= 5549)
#define NS_REQUIRES_NIL_TERMINATION __attribute__((sentinel(0,1)))
#else
#define NS_REQUIRES_NIL_TERMINATION __attribute__((sentinel))
#endif
#endif
#endif
- attribute((sentinel)) 告知编译器需要一个结尾的参数,告知编译器参数的列表已经到最后一个不要再继续执行下去了
- 最经常见到的应该是UIAlertView里用的了
- (id)initWithTitle:(NSString *)title message:(NSString *)message delegate:(id /*<UIAlertViewDelegate>*/)delegate cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ... NS_REQUIRES_NIL_TERMINATION;
VA_LIST,VA_START,VA_ARG,VA_END
- ** VA_LIST是C语言中解决变参问题的一组宏,在头文件<stdarg.h>**中。
- 用法:
- 首先在函数里定义一具VA_LIST型的变量,这个变量是指向参数的指针;
- 然后用VA_START宏初始化刚定义的VA_LIST变量;
- 然后用VA_ARG返回可变的参数,VA_ARG的第二个参数是你要返回的参数的类型(如果函数有多个可变参数的,依次调用VA_ARG获取各个参数);
- 最后用VA_END宏结束可变参数的获取。
下面造个轮子代码:
- (NSMutableArray *)setupArrWithObjects:(NSString *)arr1, ...NS_REQUIRES_NIL_TERMINATION
{
NSMutableArray *arrM = [NSMutableArray array];
va_list list;
id tag;
va_start(list, arr1);
while ((tag = va_arg(list, id))) {
[arrM addObject:tag];
}
va_end(list);
return arrM;
}
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableArray *arrM = [self setupArrWithObjects:@"arr1", @"arr2", @"arr3", nil];
NSLog(@"arrM = %@", arrM);
}
注意点
- 上述代码打印如下:
2017-02-20 17:43:42.305 argsTest[11260:282247] (
arr2,
arr3
)
因为第一个参数并不属于可变参数列表的一部分,所以va_start函数默认是从第一个参数以后开始扫描读取的。所以一般我们是手动处理第一个参数的,如下举个例子:
- (void)addObj:(id)firstObj,...NS_REQUIRES_NIL_TERMINATION
{
NSMutableArray *array = [NSMutableArray array];
if (firstObj)
{
va_list argsList;
[array addObject:firstObj]; // 手动处理第一个参数
va_start(argsList, firstObj);
id arg;
while ((arg = va_arg(argsList, id)))
{
[array addObject:arg];
}
va_end(argsList);
}
NSLog(@"array---%@", array);
}
- 第二个注意的地方是使用- (NSMutableArray )setupArrWithObjects:(NSString )arr1, ...NS_REQUIRES_NIL_TERMINATION; 时,方法中如果不传入nil值会导致程序崩溃,因为在
va_arg(argList, id))
会一直取出值,在C语言中指针指向的即便是一个空内存地址未初始化也是会取出值的,那未初始化的内存空间赋值给可变数组就出现问题了,所以在使用- (NSMutableArray )setupArrWithObjects:(NSString )arr1, ...NS_REQUIRES_NIL_TERMINATION方法时在多参数的结尾一定要加上nil.