如何在Swift和Objective-C中使用C++代码

发布于:2016-10-27 20:23,阅读数:2235,点赞数:11


> 本文将介绍如何在 iOS 和 Mac 开发中使用 C++ 代码。
>
> 本文只讨论了源码混编过程,并不涉及只有头文件的二进制库的使用过程。

# 引言

在跨平台开发中,一类是使用 Web 相关技术,另一类是使用 C++ 开发后进行移植。在一些性能要求比较高又需要跨平台的场合下使用 C++ 开发是非常常见的。一方面是 C++ 有较高的执行效率和较好的跨平台兼容性,另一方面是 C++ 开发人员更加注重性能。相比 C 语言来说,C++ 提供了面向对象封装和更强大的STL库。许多成熟的框架如`FFmpeg`等都是由 C++ 实现的。Xcode是支持 C++ 代码编译的,我们可以很轻松地在项目中混编 C++ 代码。

# Objective-C 混编

Objective-C 混编 C++ 名为 Objective-C++。

在 oc 中混编 C++ 代码,我们用的方法是封装(`wrap`)。即在 C++ 类的外面封装一层 Objective-C 类。C++ 对象的生命周期是手动管理的,而在 iOS 或 Mac 开发中,ARC 是非常棒的功能。在 C++ 类外封装一层 Objective-C 类以后,就能巧妙利用 ARC 帮我们管理对象的生命周期了。

写法非常简单,但写法上有个坑,在例子中说明:

- 新建 C++ 类:

`EMObjectCpp.hpp`:

```c++
#ifndef EMObjectCpp_h
#define EMObjectCpp_h

class EMObjectCpp
{
public:
EMObjectCpp();
~EMObjectCpp();
};

#endif /* EMObjectCpp_h */
```

`EMObjectCpp.cpp`:

```c++
#include "EMObjectCpp.h"

EMObjectCpp::EMObjectCpp()
{
printf("init\n");
}


EMObjectCpp::~EMObjectCpp()
{
printf("deinit\n");
}
```

- 新建 Objective-C 类:
`EMObject.h`:

```objectivec
#import <Foundation/Foundation.h>
#include "EMObjectCpp.hpp"

@interface EMObject : NSObject

- (nonnull instancetype)init;
- (void)dealloc;


@end
```
在 oc 类中,我们需要加入刚刚创建的 C++ 类作为实体,就像这样:

```objectivec
@interface EMObject : NSObject {
@private
EMObjectCpp * __obj;
}
@end
```

在这里,如果你在头文件中直接`#include "EMObjectCpp.hpp"`,然后试图在代码中使用`EMObjectCpp`类的话,你会得到一个编译错误:
![](//cdn.yuusann.com/img/posts/16006_1.jpg)
其原因是,在 Objective-C 的语法中`class`是非法的关键字。此处编译器将 C++ 源文件当成 Objective-C 源文件编译显然是有问题的。编译器在`预编译`阶段会把`include`的头文件贴到源文件里进行预编译,所以在这里我们必须要保证不出现 C++ 的代码。所以在此处我们只能用扩展(`Extension`)去加入 C++ 类。另外,需要将源文件扩展名改成`.mm`才能启动 Xcode 的 C++ 支持。所以 Objective-C 类的实现是这个样子的:

~~`EMObject.m`~~:

`EMObject.mm`:

```objectivec
#include "EMObjectCpp.hpp"
#include "EMObject.h"

@interface EMObject () {
@private
EMObjectCpp * __obj;
}
@end

@implementation EMObject

- (instancetype)init
{
self = [super init];
if (self) {
__obj = new EMObjectCpp();
}
return self;
}

- (void)dealloc
{
delete __obj;
__obj = NULL;
}
@end
```

现在已经可以编译通过了,对象也可以正确创建,也可以在 AutoreleasePool Pop 的时候正常地被 ARC 释放。

另外 Xcode 默认是支持 STL 的,因此在这里加入了两个简单的方法。完整源码如下:

`EMObjectCpp.hpp`:

```c++
#ifndef EMObjectCpp_hpp
#define EMObjectCpp_hpp

#include <string>

class EMObjectCpp
{
public:
EMObjectCpp();
~EMObjectCpp();
void sayHello();
std::string getString();
};

#endif /* EMObjectCpp_hpp */
```

`EMObjectCpp.cpp`:

```c++
#include "EMObjectCpp.hpp"


EMObjectCpp::EMObjectCpp()
{
printf("init\n");
}


EMObjectCpp::~EMObjectCpp()
{
printf("deinit\n");
}

void EMObjectCpp::sayHello()
{
printf("Hello world!\n");
}

std::string EMObjectCpp::getString()
{
return "Hello STL";
}
```

`EMObject.h`:

```objectivec
#import <Foundation/Foundation.h>

@interface EMObject : NSObject

- (nonnull instancetype)init;
- (void)dealloc;

- (void)sayHello;
- (nonnull NSString *)getString;

@end
```

`EMObject.mm`:

```objectivec
#include "EMObjectCpp.hpp"
#include "EMObject.h"

@interface EMObject () {
@private
EMObjectCpp * __obj;
}
@end

@implementation EMObject

- (instancetype)init
{
self = [super init];
if (self) {
__obj = new EMObjectCpp();
}
return self;
}

- (void)dealloc
{
delete __obj;
__obj = NULL;
}

- (void)sayHello
{
__obj->sayHello();
}

- (NSString *)getString
{
return [NSString stringWithUTF8String:__obj->getString().c_str()];
}

@end
```

测试代码:

```objectivec
#import <Foundation/Foundation.h>
#include "EMObject.h"


int main(int argc, const char * argv[]) {
@autoreleasepool {
EMObject * obj = [EMObject new];
[obj sayHello];
printf("%s\n", [obj getString].UTF8String);
}
return 0;
}
```

运行结果:
![](//cdn.yuusann.com/img/posts/16006_2.jpg)

# Swift 混编
事实上 Swift 并不能直接使用 C++ 类,因此我们要做的是在上面的基础上将 Objective-C 代码桥接到 Swift 中。
桥接方法很简单,新建一个头文件:

`bridge.h`:

```objectivec
#ifndef bridge_h
#define bridge_h

#include "EMObject.h"

#endif /* bridge_h */
```

在工程的此处加入桥接头文件的相对路径和文件名:
![](//cdn.yuusann.com/img/posts/16006_3.jpg)
然后在 Swift 代码中直接使用就可以了。
测试代码:

```swift
import Foundation

autoreleasepool {
let obj = EMObject.init()
obj.sayHello()
print("\(obj.getString())")
}
```

运行结果同上。

# 结语
在 iOS 和 Mac 项目中使用 C++ 代码还是比较方便的。只要绕过了封装 Objective-C 类时写法上的小坑就可以了。至于 oc 桥接到 Swift 更是方便,苹果在这方面替我们铺好了路,方便我们迁移项目。苹果选择了 oc 是历史原因,当时 oc 还是十分先进的。一直没有机会换掉它,就沿用到了今天。现在终于有机会换掉它了,Swift 是为了替代 oc 的地位而诞生的,也相信它最终也会走向成功。


评论:0条


返回列表

返回归档

返回主页