2017-09-01 115 views
1

我的问题是:为啥仅在使用 x86_64 编译时会出现此编译错误?以及如何解决?
My question is: why only when select x86_64 simulator compile error occurs? And how to solve?why Complie Error only when select x86_64 simulator "Cannot refer to declaration with an array type inside block"

环境: Xcode v8.3.2
测试代码:

+ (BOOL)updateSqlByFileName:(NSString *)file key:(NSString *)key, ... 
{ 
    va_list args; 
    va_start(args, key); 

    __block BOOL isOK = NO; 
    [_queue inDatabase:^(FMDatabase *_dataBase)// 
    { 
     isOK = [_dataBase executeUpdate:sql withVAList:args]; 
    }]; 

    va_end(args); 
    return isOK; 
} 
不同的编译方式,编译错误情况
  • 【编译正常√】选择 Generic iOS Device 编译(Build)时:【armv7 + arm64】

    CompileC Test.m normal armv7 objective-c com.apple.compilers.llvm.clang.1_0.compiler 
    CompileC Test.m normal arm64 objective-c com.apple.compilers.llvm.clang.1_0.compiler 
    
  • 【编译正常√】 选择真机: iPhone 4(7.1.2)编译(Build)时:【armv7】

    CompileC Test.m normal armv7 objective-c com.apple.compilers.llvm.clang.1_0.compiler 
    
  • 【编译错误×】选择模拟器: iPhone 5s(10.3),iPhone SE(10.3) ,iPhone 7 Plus(10.3)编译(Build)时:【x86_64】

    CompileC Test.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler 
    

    Complie Error :
    Cannot refer to declaration with an array type inside block

  • 【编译正常√】选择模拟器: iPhone 5(10.3)编译(Build)时:【i386】

    CompileC Test.m normal i386 objective-c com.apple.compilers.llvm.clang.1_0.compiler 
    

回答

2

If a block captures a non- __block variable, a copy of the variable is made when the block is created and stored within the block. Arrays cannot be assigned in C, and that may be why the designers of blocks disallowed capturing variables of array type in a block.

What kind of type va_list is is not specified by the C standard; it is implementation-specific. It could be implemented as an array type, a pointer type, a struct type, or whatever, and this can be different across architectures on the same compiler. Probably they implemented it as an array type in x86_64 and as some non-array type on the other 3 architectures. There is nothing unusual about this. You cannot make assumptions about what kind of type va_list is.

In your answer where you defined another function and passed the va_list to it, that happens to work in the case where va_list is an array type, because C automatically adjusts any parameters of type "array of T" to "pointer to T", so the parameter args in the method +foo:key:withVAList: actually has a pointer type when va_list is an array type (different type than the args in the +foo:key: method), and pointer variables can be captured into a block fine.

另一种解决方案是取va_list的地址,得到一个va_list *并将其放入块中使用的变量中。无论va_list是什么,这都保证是一个指针类型,并且可以很好地捕获块。当您需要使用块内的实际va_list时,可以取消引用指针。例如,像这样的:

+ (BOOL)updateSqlByFileName:(NSString *)file key:(NSString *)key, ... 
{ 
    va_list args; 
    va_start(args, key); 
    va_list *ptrToArgs = &args; 

    __block BOOL isOK = NO; 
    [_queue inDatabase:^(FMDatabase *_dataBase) 
    { 
     isOK = [_dataBase executeUpdate:sql withVAList:*ptrToArgs]; 
    }]; 

    va_end(args); 
    return isOK; 
} 
-2
经过尝试【有效的】解决方案(代码):
+ (BOOL)foo:(NSString *)file key:(NSString *)key, ... 
{ 
    va_list args; 
    va_start(args, key); 

    BOOL isOK = [self foo:file key:key withVAList:args]; 

    va_end(args); 
    return isOK; 
} 

+ (BOOL)foo:(NSString *)file key:(NSString *)key withVAList:(va_list)args 
{ 
    __block BOOL isOK = NO; 
    [_queue inDatabase:^(FMDatabase *_dataBase){ 
     isOK = [_dataBase executeUpdate:sql withVAList:args]; 
    }]; 
    return isOK; 
} 
参考代码(灵感来源):

FMDB类库里的 executeQuery 函数

- (FMResultSet *)executeQuery:(NSString*)sql, ... { 
    va_list args; 
    va_start(args, sql); 

    id result = [self executeQuery:sql withArgumentsInArray:nil orDictionary:nil orVAList:args]; 

    va_end(args); 
    return result; 
} 
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args { 
    //xxx 
}