The original address: www.transpire.com/insights/bl…

Original author:

Release date: 2014

At work recently, I completed a C++ library that both our iOS and Android apps use. To make it easier for our native iOS app, I also wrote an Objective-C wrapper so that the rest of the app could implement the interface using only Objective-C code. The job of this wrapper is basically to interface properties and setters, and it also handles C++ memory management.

This is the C++ class I’m going to use as an example. It’s pretty basic, just a constructor, a setter, and a 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(a) const { return string_; }
	
private:
	char* string_;
};

#endif /* CPP_CLASS_H */
Copy the code

Objective-c is based on C, not C++. If you try to use C++ code in Objective-C, you will run into compiler problems. What we need is to write the wrapper implementation in Objective-C++. Objective-c files have a.m extension, and to use Objective-C++, all you need to do is change the file extension to.mm.

You don’t want to include any C++ classes or code in the objective-C class header of the wrapper, if possible. If you do, then any classes that import it will also need to have a.mm extension. You can get around this with some preprocessors, but it’s rare that you need to put variables in the header.

So for objective-C headers, I’m going to keep it extremely simple, just containing an initializer and attributes. According to the following, the other classes don’t even know that C++ supports this class.

@interface ObjcClass : NSObject

@property (nonatomic.copy) NSString* string;

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

@end
Copy the code

For implementation, first remember to set.mm as your file extension. You will need to store a pointer to the C++ class you are instantiating. We can do this in an empty category rather than in the header.

@interface ObjcClass(a)

@property (nonatomic.readonly) CppClass* internal;

@end
Copy the code

My initWithString: method simply assigns a CppClass to an internal variable and assigns the method any string.

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

To make things as “objective-C” as possible, I convert between Objetive-C and C++ types. C++ classes use const char to store strings, but to make things as native as possible in the initializer, I converted it from NSString to const char.

If I had other properties and types, I would do the same thing. Even enums and structs that wrap C++, so only my Objetive-C code is exposed in the public interface.

In the getter and setter for my string property, I’m going to do the same conversion.

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

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

Finally, this is C++, so we need to do some manual memory management. Whenever we are done with a C++ allocated object, we need to delete it. This belongs to the Dealloc method.

- (void) dealloc
{
	delete _internal;
}
Copy the code

The whole class.

#import "CppClass.h"

@interface ObjcClass(a)

@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
Copy the code

The class in my example is obviously very simple. There are more challenges and other considerations when encapsulating large libraries. including

  • Return other classes that also require an Objective-C wrapper.
  • Smart Pointers are used, so even C++ memory management is handled automatically.
  • Calling the same method twice returns the same object (if you call object.string above, you get a different pointer each time, even if the value does not change).