Swift Is Not A Panacea

Do you have a large Objective-C code base and feel missing out on some of the nice features of Swift, such as: optionals, type inference, tuples and higher order functions such as filter, map and reduce? Or maybe you want to utilize a C++ library but don’t want to wrap every API call in C or Objective-C, since Swift cannot call into C++ libraries directly as of Swift 4 and Xcode 9. It is worse if the API -C, Objective-C or C++- uses pointers either as parameters or return type, Swift makes using pointers complex and daunting, aggravated by an inelegant syntax that’s hard to remember, and a compiler that won’t allow type punning. Simple things such as the following which is trivial in C, Objective-C and C++, -while possible- is not trivial to write in Swift.

// Simple and straightforward, now try something similar in Swift

while (*dst++ = *src++);

While Xcode allows you to use both Objective-C and Swift in the same project, they remain siloed in separate source files and there are strict rules regarding how they are mixed, and you will find yourself rewriting perfectly functioning code for the promise of something simpler and more concise. Maybe you just don’t want to use Swift, or maybe you don’t want to throw away your Objective-C code. Luckily there is an alternative, you can add the above mentioned features to your existing Objective-C code, not just at the project level, but at the code unit level, e.g. inside a method or class, without the need for a major refactoring or rewrite and it is called Objective-C++, and these features can be added iteratively and freely without breaking your existing code or requiring a significant rewrite.

C++ Strikes Back

Objective-C++ is not a programming language, instead it allows the mixing of C++ and Objective-C in the same source file, the source file only need to have the extension .mm or the source code type need to be specified as Objective-C++ Source in Xcode. C++ might have been archaic 10 years ago, but since C++11 -now C++17- it has been rapidly evolving, adding modern niceties without losing its power and flexibility. In the past 7 years, C++ gained automatic type inference, smart pointers, lambda expressions, constant expressions, move semantics, range-based for-loops, explicit override and final, null pointer constant, rvalue references, function return type deduction, multithreading, string view, optionals, and much more.

Objective-C++ is not only fully supported by Apple, it is actively used by WebKit, so you can rest assured that it isn’t going anywhere anytime soon. Objective-C++ works amazingly well thanks to the power and flexibility of C++ templates and standard library. C++ containers behave as if they understood Cocoa objects, there are some limitations here and there that can be worked around. Note though Cocoa containers, e.g. NSArray, aren’t as flexible and only accept objects that inherit from NSObject. For example you can do the following:

// Create a C++ std::vector object of Foundation NSString objects

std::vector<NSString*> myVec = {@"This", @"is", @"a C++ vector", @"of NSString objects"};



// Use C++'s range-based for-loop to iterate over the vector object

// Objective-C's fast enumeration doesn't know about C++ objects

// auto performs compile time type inference

// C++ knows that str is NSString

for(auto str : myVec) {

// std::cout doesn't work with NSString directly// UTF8String method of NSString returns a UTF8 C string

std::cout << [str UTF8String] << std::endl;

}

Now this isn’t the best example of using C++ in Objective-C. This doesn’t add anything over what you can do in Objective-C except for type inference with ‘auto’ keyword. In fact, range-based for-loop is slower than Objective-C’s fast enumeration. But it does a good job of illustrating how seamless the integration is. For something more useful, you could do the following:

// a vector with nullptr member is perfectly valid in C++

auto vector = std::vector<NSString*>{@"a", @"b", nullptr, @"d"};

// you can use nil and nullptr interchangeably, "It Just Works"

vector.erase(remove(begin(vector), end(vector), nil), end(vector));



// the vector now has @"a", @"b", @"d"

for(auto string : vector) {

std::cout << [string UTF8String] << std::endl;

}

std::remove is one of the many powerful functions available in <algorithm> library that operate on C++ containers, I suggest those who still not sure why they should use C++ to take a look at it. There’s many more functions, many of them take a Lambda function as a parameter. You can think of a Lambda function is a nicer Objective-C block that won’t require you to visit GoshDarnBlockSyntax to remember what the syntax was. The following is such an example:

auto vector = std::vector<NSString*>{@"Apple", @"Atom", @"Atari"};



// True

if (std::all_of(vector.cbegin(), vector.cend(), [](NSString* str){ return [str hasPrefix:@"A"]; })) {

std::cout << "All strings begin with A

";

}



// True

if (std::any_of(vector.cbegin(), vector.cend(), [](NSString* str){ return [str hasPrefix:@"A"]; })) {

std::cout << "At least one string begins with A

";

}



// False

if (std::none_of(vector.cbegin(), vector.cend(), [](NSString* str){ return [str hasPrefix:@"A"]; })) {

std::cout << "None of the strings begin with A

";

}

A typical Lambda begins with a capture block which allows you to capture either by value or reference, then the parameter list, and finally the lambda body. There are three optional components to a Lambda, you can learn more about them here.

There are rough edges though with some of the newer C++ features such as std::optional which was introduced in C++17. This may be due to its status as experimental as of Xcode 9, or perhaps due to Objective-C’s ARC resulting in Cocoa objects having non-trivial ownership. This could be worked around by either disabling ARC which can be done on a file-by-file basis or alternatively bridging the Cocoa objects to their toll-free bridged CoreFoundation counterparts. To disable ARC in a file, go to the target’s Build Phases tab, and under Compile Sources select the file you want to disable ARC for and add the following flag -fno-objc-arc

// As of Xcode 9.2, optional is still under experimental

using std::experimental::optional; // Use CFStringRef instead of NSString*, otherwise disable ARC

optional<CFStringRef> optionalString(bool val) {

optional<CFStringRef> myOptString; if(val) {

// Cast to corresponding CoreFoundation object

myOptString = (CFStringRef)@"String";

} return myOptString;

} int main() {

// value_or() performs nil coalescing, returning the value passed to it if the optional is empty

auto str = optionalString(false).value_or(CFSTR("Empty"));

// Cast back to NSString* and print using NSLog()

NSLog(@"%@", (__bridge NSString*)str); // prints "Empty"



return 0;

}

My personal favorite though, is using std::future and std::promise to wait on the result of a background libDispatch queue. Typically you don’t want to wait on the result of an async call, but for the cases that you want to do so, C++ std::future and std::promise have simpler syntax and semantics than libDispatch’s synchronization functions. As in the following example:

__block std::promise<bool> promObj;

std::future<bool> futuObj = promObj.get_future();



[asyncCallWithReply:^(bool success) {

// set the promise object with the reply received

promObj.set_value(success);

}];



// set maximum wait time, here we are setting it to 1 second

std::future_status status = futuObj.wait_for(std::chrono::seconds(1));

if(status == std::future_status::ready) {

// we got a reply before timeout

return futuObj.get();

} else {

// took too long to reply

return false;

}

If we were using libDispatch exclusively, it would look something like this:

__block bool status;

dispatch_group_t syncGroup = dispatch_group_create();

dispatch_group_enter(syncGroup);



[asyncCallWithReply:^(bool success) {

status = success;

dispatch_group_leave(syncGroup); }];



// set maximum wait time, here we are setting it to 1 second

dispatch_time_t waitTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(NSEC_PER_SEC * 1)); if(dispatch_group_wait(syncGroup, waitTime) == 0) {

// we got a reply before timeout

return status;

} else {

// took too long to reply

return false;

}

The Objective-C++ example that makes use of both libDispatch and std::promise and std::future is easier to reason with than the pure Objective-C and libDispatch solution, and requires fewer lines of code.

While C++ and Objective-C types are incompatible, you could make use of categories in Objective-C to add methods to Cocoa objects that will return their corresponding C++ objects, and vice-versa, as in the following example:

// Factory method to create NSString from C++ string

+ (NSString*)stringWithCppString:(std::string)cppString {

return [NSString stringWithUTF8String:cppString.c_str()];

} // Instance method that returns C++ string from NSString

- (std::string)cppString {

return std::string([self UTF8String]);

}

Similarly, NSData is commonly used by iOS and macOS developers who want to operate on raw bytes, however, NSData is pretty weak as it is merely a wrapper around a C void pointer. std::vector can provide the same functionality with better control, and it is easy to get the raw C data pointer if needed. You can add the following category to NSData.

+ (NSData*)dataWithByteVector:(std::vector<uint8_t>) v {

return [NSData dataWithBytes:v.data() length:v.size()];

} - (std::vector<uint8_t>)byteVector {

uint8_t* bytes = (uint8_t*)[self bytes];

return std::vector<uint8_t>(bytes, bytes + [self length]);

}

Then, data can be easily extracted from the byte vector without having to repeatedly specify the data size, you only need to specify where the data begins in the byte vector, with a simple function template such as this:

template <typename T>

T extract(const std::vector<uint8_t> &v, int pos)

{

T value;

memcpy(&value, &v[pos], sizeof(T));

return value;

} // extracting a float at position 6 of bytes vector

extract<float>(bytes, 6); // extracting a char at position 10 of bytes vector

extract<char>(bytes, 10);

Gotchas and Conclusion

So there’s no gotchas? Wrong! There obviously are, as all programmers know there are serious incompatibilities between C and C++, there is valid C code that isn’t valid in C++, and by changing your source code from Objective-C to Objective-C++ you risk running into them. Objective-C is a superset of C, where all valid C code is valid Objective-C code, and since Objective-C programmers don’t shy from using C, they will have to take them into account. What about Objective-C programmers that never venture outside of the Objective-C runtime and Cocoa? There are still incompatibilities between Objective-C and C++ that they need to consider, especially if they have ARC enabled which should be everyone by now, but as a previous example illustrated it is fairly easy and straightforward to work around the “Field has non-trivial ownership qualification” error message that will be generated when hitting that rough edge, and it will require wrapping Cocoa objects since C++ containers do not know about retain and release, or memory will leak. It is also worth repeating that Cocoa containers can only contain that which inherits from NSObject, but you wouldn’t be using Objective-C++ if you didn’t want to use C++ containers as C++ containers can contain Cocoa objects, and the powerful and flexible C++ functions you would want to use require C++ containers.

Additionally the object model and class hierarchy is also incompatible. C++ classes cannot inherit from Objective-C classes, and Objective-C classes cannot inherit from C++ classes, neither can a class of one be cast into a class of another. Though with a little bit of glue you could work around this limitation, since both C++ and Objective-C classes can have members that are objects of one another’s classes. You can for example use the PIMPL model to defer the implementation of an Objective-C class to a C++ class and vice-versa. You can also make use of the fact that both Objective-C and C++ share a common subset through C, and that many of Objective-C and C++ objects either return or have a corresponding C object that can function as a common low level denominator.