在软件开发过程中,经常需要在Objective-C代码中使用C++代码,或者在Objective-C中调用C函数。本文旨在探讨Objective-C++的概念,即在同一个文件中混合使用Objective-C和C++代码,并实现它们之间的互操作。在XCode中,可以通过将文件扩展名命名为.mm来使用Objective-C++。
当尝试在block中使用一个在外部声明的std::ifstream变量时,可能会遇到以下代码示例中的问题。
Objective-C
__block std::ifstream file("/tmp/foo");
//
With and without __block
void (^block)() = ^{
file.rdbuf();
file.close();
file.open("/tmp/foo");
};
block();
如果使用__block修饰符声明变量,将得到以下错误消息(Apple LLVM 4.2(Clang)):
«main.mm:28:27: Call to implicitly-deleted copy constructor of 'std::ifstream' (aka 'basic_ifstream')»
在最新的clang(XCode5.0)中,不会得到任何错误消息。也提交了一个关于这个问题的错误报告:。在最新版本中,std::ifstream的复制构造函数并没有被删除。
如果不使用这个修饰符,那么另一个错误:
«main.mm:31:9: Call to implicitly-deleted copy constructor of 'const std::ifstream' (aka 'constbasic_ifstream')
main.mm:32:9: Member function 'close' not viable: 'this' argument has type 'const std::ifstream' (aka 'constbasic_ifstream'), but function is not marked const»
GCC将产生类似的错误消息。然而,如果在GCC中使用__block,首先会发生内部编译器错误:
«Internal compiler error: Segmentation fault: 11»
解决方案是使用指针来操作对象。这对于Clang和GCC都是正确的。
假设想要将一些Objective-C对象作为参数传递给C函数,例如传递给sqlite3_exec。
Objective-C
NSMutableArray *rowsArray = [[NSMutableArray alloc] init];
sqlite3_exec(database, "select distinct category from persons", callback, rowsArray, NULL);
当ARC开启时,将得到编译错误:
«main.m:36:48: Implicit conversion of Objective-C pointer type 'NSMutableArray *' to C pointer type 'void *' requires a bridged cast»
ARC不允许如此简单地进行这种转换。
需要使用桥接转换来在两种内存模型之间移动。这种转换的最简单形式是(XCode本身在代码中悬停错误时提供):
Objective-C
sqlite3_exec(database, "select distinct category from persons", callback, (__bridge void *)rowsArray, NULL);
这与第一行代码等效,但现在,由于在ARC模式下,有一个额外的注意事项。问题是ARC可以释放rowsArray,如果它是它的最后一个引用。不幸的是,sqlite3_exec对对象一无所知,所以不会保留参数。到函数被调用时,对象可能已经被移除了。正确的解决方案是:
Objective-C
sqlite3_exec(database, "select distinct category from persons", callback, (__bridge_retained void *)rowsArray, NULL);
这将在传递参数之前调用objc_retain()。
另一方面,回调函数应该看起来像这样:
Objective-C
static int myCallback(void * ptr, int i, char ** p1, char ** p2) {
NSMutableArray* rowsArray = (__bridge_transfer NSMutableArray *)ptr;
ptr = NULL; // Do something with rowsArray here
return 0;
}
这将把对象重新置于ARC的控制之下。这种桥接转换的形式期望对象已经被保留,因此将在作用域结束时释放它(调用release)。ptr = NULL;这行是可选的,但它是一个好风格。已经将ptr包含的引用置于ARC的控制之下,所以重置指针是明确的。
假设有一个Objective-C类:
@interface MyObjc : NSObject
@end
想象有一个名为MyCppClass的简单C++类。希望在Objective-C类中添加一些属性,返回MyCppClass类型的对象。
现在可以这样做,但只能与具有默认构造函数的C++对象变量一起。Clang和GCC都支持这种情况。
从Mac OS X 10.4和GCC 4开始,可以在Objective-C类中将C++对象作为实例成员变量放置,假设C++对象必须使用默认构造函数创建。GCC编译器有一个特殊的编译器选项来控制这个过程调用默认构造函数的变量«Call C++ Default Ctors / Dtors in Objective-C» (GCC_OBJC_CALL_CXX_CDTORS, -fobjc-call-cxx-cdtors)。尽管Clang没有C++构造函数/析构函数的特殊选项,但它生成了正确的C++构造函数调用。
例如,可以编写以下代码:
C++
class MyCppClass {
MyCppClass();
};
@interface MyObjc : NSObject {
MyCppClass cppVariable;
}
@property (nonatomic, assign) MyCppClass cppVariable;
@end
@synthesize cppVariable;