[iOS翻译]为C++编写一个Objective-C包装器。

226 阅读3分钟

原文地址:www.transpire.com/insights/bl…

原文作者:

发布时间:2014年

最近在工作中,我完成了一个C++库,我们的iOS和Android应用都在使用这个库。为了方便我们的原生iOS应用使用,我还写了一个Objective-C包装器,让应用的其他部分只用Objective-C代码就能实现接口。这个包装器的工作基本上是衔接属性和设置器,同时也处理C++的内存管理。

这是我要用的C++类作为例子。它很基本,只是由一个构造函数、一个setter和一个getter组成。

#ifndef CPP_CLASS_H
#define CPP_CLASS_H

#include <cstring>
#include <stdlib.h>

class CppClass
{
public:
	CppClass(const char* string = NULL) { set_string(string); }
	~CppClass() { set_string(NULL); }
	
	void set_string(const char* string)
	{
		if(string_)
			free(string_);
		string_ = (char*)(string ? strdup(string) : NULL);
	}
	
	const char* string() const { return string_; }
	
private:
	char* string_;
};

#endif /* CPP_CLASS_H */

Objective-C是基于C的,而不是基于C++的。如果你试图在Objective-C中使用C++代码,你会遇到编译器问题。而我们需要的是用Objective-C++来编写包装器的实现。Objective-C文件的扩展名是.m,要使用Objective-C++,你需要做的就是将文件扩展名改为.mm。

在包装器的Objective-C类头中,如果可能的话,你希望不要包含任何C++类或代码。如果你这样做了,那么任何导入它的类也需要有.mm的扩展名。你可以通过一些预处理程序来解决这个问题,但是很少有需要把变量放在头中的情况。

所以对于Objective-C头,我将保持极其简单,只包含一个初始化器和属性。根据下面的内容,其他的类根本不知道其实有C++在支持这个类。

@interface ObjcClass : NSObject

@property (nonatomic, copy) NSString* string;

- (instancetype) initWithString:(NSString*) string;

@end

对于实现,首先记得设置.mm作为你的文件扩展名。你将需要存储一个指向你实例化的C++类的指针。我们可以在一个空的类别中进行,而不是在头中进行。

@interface ObjcClass ()

@property (nonatomic, readonly) CppClass* internal;

@end

我的initWithString:方法只是将一个CppClass分配到内部变量中,并赋予该方法任何字符串。

- (instancetype) initWithString:(NSString*) string
{
	self = [super init];
	
	if(self != nil)
	{
		_internal = new CppClass(string.UTF8String);
	}
	
	return self;
}

为了使事情尽可能的 "Objective-C",我在Objetive-C和C++类型之间进行转换。C++类使用const char来存储字符串,但是为了在初始化器中尽可能地使事情变得原生,我将它从NSString转换为const char

如果我有其他属性和类型,我会做同样的事情。甚至包装C++的enums和structs,所以只有我的Objetive-C代码在公共接口中暴露。

在我的string属性的getter和setter中,我会做同样的转换。

- (NSString*) string
{
	const char* string = self.internal->string();
	return string ? @(string) : nil;
}

- (void) setString:(NSString*) string
{
	self.internal->set_string(string.UTF8String);
}

最后,这是C++,所以我们需要做一些手动的内存管理。每当我们处理完一个C++分配的对象,就需要删除它。这属于dealloc方法。

- (void) dealloc
{
	delete _internal;
}

整个类。

#import "CppClass.h"

@interface ObjcClass ()

@property (nonatomic, readonly) CppClass* internal;

@end

@implementation ObjcClass

- (instancetype) init
{
	return [self initWithString:nil];
}

- (instancetype) initWithString:(NSString*) string
{
	self = [super init];
	
	if(self != nil)
	{
		_internal = new CppClass(string.UTF8String);
	}
	
	return self;
}

#pragma mark -
#pragma mark Self

- (NSString*) string
{
	const char* string = self.internal->string();
	return string ? @(string) : nil;
}

- (void) setString:(NSString*) string
{
	self.internal->set_string(string.UTF8String);
}

#pragma mark -
#pragma mark Cleanup

- (void) dealloc
{
	delete _internal;
}

@end

我所举的例子中的类显然是非常简单的。在封装大型库时,还有更多的挑战和其他需要考虑的事情。其中包括

  • 返回同样需要Objective -C封装器的其他类。
  • 使用智能指针,因此即使是C++的内存管理也会自动处理。
  • 调用同一个方法两次,返回同一个对象(如果你调用上面的object.string,即使值没有变化,每次都会得到不同的指针)。