聊聊你不知道的 Objective-C++

发布于:2017-04-16 13:39,阅读数:4352,点赞数:19


# 引言 最近正在在开发一款`iOS`和`macOS`通用的`OpenCV`图像处理开源库。虽然`OpenCV`存在一套C语言接口,可以直接在 Objective-C 中使用,但不管是从语法层面还是内存管理上来说,它的 C++ 接口更好。而 Objective-C 本身是不支持 C++ 的,因此笔者采用了 Objective-C++ 来开发这套库,并在这里简单探讨一下 Objective-C++ 的内容。 本文中会使用正在开发的开源库`EMCVLib`中的一些代码作为例子,项目地址:[https://github.com/trmbhs/EMCVLib](https://github.com/trmbhs/EMCVLib) # Objective-C++ ## 什么是 Objective-C++ 正如它的名字,Objective-C 加上 C++ 就是 Objective-C++。苹果允许开发者在同一个源文件中混编 Objective-C 和 C++,混编后的语言就称为 Objective-C++。 Objective-C++ 是一门语言,毕竟 GitHub 都能直接认出它。 ![](//cdn.blog.yuusann.com/img/posts/17013_1.png) ## 为什么要使用 Objective-C++ Objective-C 是C语言的扩展,完全兼容C语言,但它并不兼容 C++。市面上有非常多的跨平台开源库使用的是 C++。虽然一部分库提供了C语言接口,但它们并不是那么好用。例如著名的图像库`OpenGL`,开发者利用C语言接口维护的是一个状态机,一个服务端。又例如`Android NDK`中的`MediaCodec`接口,需要频繁传入句柄去操作,这种操作明显是可以被面向对象开发代替的。 说到底,是为了更好地面向对象编程,更好地在苹果各个平台上使用第三方开源的 C++ 库。 ## 如何使用 Objective-C++ 在 Xcode 中,正常情况下的 Objective-C 源文件为`.m`源文件,将后缀名改成`.mm`即可开启 C++ 支持。在 Objective-C++ 中,开发者可以完美地在 Objective-C 类中嵌入 C++ 属性和方法。 说了这么多,来看一个例子: ```objectivec #import <Foundation/Foundation.h> //OpenCV头文件,里面包C++类 #include "opencv.h" //省略其他头文件 //C++ using namespace std; using namespace cv; @interface EMCVSplitedImage : NSObject //C++ { @public vector<Mat> _mats; vector<MatND> _hists; } - (instancetype)initWithMats:(vector<Mat>)mats; - (instancetype)initWithNoCopyMats:(vector<Mat>)mats; @property (nonatomic, readonly) int channalCount; - (instancetype)initWithPath:(NSString *)path; - (EMCVImage *)mergeImage; //省略其他方法 @end ``` 这是库中的一个类的头文件代码片段。笔者引入了包含 C++ 类的头文件`opencv.h`,在 Objective-C 类中加入了 C++ 属性`_mat`和`_hists`。且这两个属性的类型是`std::vector<cv::Mat>`和`std::vector<cv::MatND>`。同时,在下面的两个构造函数参数中使用了 C++ 数据结构。在 Objective-C++ 中,Objective-C 和 C++ 完美融合了。 ```objectivec #import "EMCVSplitedImage.h" //省略其他头文件 @implementation EMCVSplitedImage - (int)channalCount { return (int)self->_mats.size(); } - (instancetype)initWithMats:(vector<Mat>)mats { vector<Mat> newMats; for_each(mats.cbegin(), mats.cend(), [&newMats](const Mat mat)->void { Mat newMat; mat.copyTo(newMat); newMats.push_back(newMat); }); return [self initWithNoCopyMats:newMats]; } - (instancetype)initWithNoCopyMats:(vector<Mat>)mats { self = [super init]; if (self) { self->_mats = mats; } return self; } - (EMCVImage *)mergeImage { return [[EMCVImage alloc] initWithCVSplitedImage:self]; } //省略其他方法实现 @end ``` 这是`EMCVSplitedImage`对应的`.mm`实现文件。在 Objective-C 中,使用`object.property`来访问对象属性,在 C++ 中,使用`object->property`或`*object.property`来访问对象属性。而在 Objective-C 中,开发者可以使用这两种方法访问各自的属性。Objective-C 中完全遵守原来的语法规定,C++ 也完全遵守原来的语法规定。同样的,两者完美融合了。 不光如此,C++ 的标准库给开发者带来了大量的工具,例如`std::vector`向量容器,`std::for_each`枚举器等。使用其他第三方 C++ 库也是如此,开发者可以在 Objective-C 方法的实现中嵌入 C++ 实现,去构造 C++ 类,去访问 C++ 接口,去使用`std`的工具类,没有任何问题。 例如这样: ```objectivec std::vector<NSObject *> vector; NSObject * obj = [[NSObject alloc] init]; vector.push_back(obj); ``` ## 类接口多态性 这是 Objective-C++ 中非常有意思的特性,我称它为类接口多态。一个类,可以由两种方式呈现:纯 Objective-C 类和Objective-C++类。以不同语言接入时,这个类会表现出不同的属性,不同的接口。同样的,来看一个例子: ```objectivec #import <Foundation/Foundation.h> //OpenCV头文件,里面包C++类 #ifdef __cplusplus #include "opencv.h" //省略其他头文件 //C++ using namespace std; using namespace cv; #endif @interface EMCVSplitedImage : NSObject #ifdef __cplusplus //C++ { @public vector<Mat> _mats; vector<MatND> _hists; } - (instancetype)initWithMats:(vector<Mat>)mats; - (instancetype)initWithNoCopyMats:(vector<Mat>)mats; #endif @property (nonatomic, readonly) int channalCount; - (instancetype)initWithPath:(NSString *)path; - (EMCVImage *)mergeImage; //省略其他方法 @end ``` 同样是存在于`EMCVLib`中的代码片段。由于引入了`opencv.h`这个头文件,包含了 C++ 相关内容,在纯 Objective-C 类接入这个类时,由于纯 Objective-C 类无法识别 C++ 的关键字,会发生无法编译的问题。这就意味着所有接入这个类的语言都必须是 Objective-C++ 了吗?答案是否定的。 在上面的例子中,笔者加入了预编译命令`#ifdef __cplusplus`,当外部调用者是纯 Objective-C 时,C++ 相关属性和方法对其是不可见的,对它来说,这就是个纯 Objective-C 类。但自然,它也无法直接访问这些属性和方法,毕竟在例子中,它连参数的类型`cv::Mat`是个什么东西都不知道。而当接入者是 Objective-C++ 时,它就可以访问所有属性和方法了。 ## 存在的问题 Objective-C++ 如此强大,但也并不是完美的。 ### 类 Objective-C 的类和 C++ 的类在底层实现上是有很大不同的,最明显的表现就是动态性。Objective-C 的类为了实现动态性,在编译时候是会做很多工作的,例如构建方法表等具体不展开,但是 C++ 不会。Objective-C 非常多的功能都依赖动态性例如 KVO,这些是不能用在 C++ 对象上的。 因此这两者并非完美融合,但是还是有非常有意思的用法,例如把 C++ 对象放进 `NSArray`: ```objectivec CppObject * cppObj = new CppObject(); NSValue * value = [NSValue valueWithPointer:cppObj]; NSArray * array = @[value]; ``` ### ARC 桥接 ARC 对象指针向C语言指针转换时需要手动指定是否增加引用计数,这个问题并不是 Objective-C++ 的问题,相信很多开发者已经遇到过了。例如`NSObject *`转向`void *`时。 ### 编码体验 将源文件后缀名改为`.mm`后,Xcode 的代码提示会变得非常非常慢。 # 结语 Objective-C++ 能帮助开发者更好地在项目中集成 C++ 内容。类接口多态性能帮助开发者在语言层面选择性地暴露接口,在封装 C++ 库的过程中尽可能不将 C++ 细节暴露给用户。 考虑到目前 Swift 不能直接调用 C++ 类,因此本次开源库还是采用了 Objective-C 来开发。


评论:0条


返回列表

返回主页