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 /*<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.
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容