原文地址: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,即使值没有变化,每次都会得到不同的指针)。