在此處踩過坑,故留記一下。
先看段代碼:
- (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.