FMDB中VA_LIST,NS_REQUIRES_NIL_TERMINATION注意点

在此处踩过坑,故留记一下。

先看段代码:

- (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 /**/)delegate cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ... NS_REQUIRES_NIL_TERMINATION;  

VA_LIST,VA_START,VA_ARG,VA_END

  • ** VA_LIST是C语言中解决变参问题的一组宏,在头文件**中。
  • 用法:
    • 首先在函数里定义一具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.

你可能感兴趣的:(FMDB中VA_LIST,NS_REQUIRES_NIL_TERMINATION注意点)