I'm writing a large project for iOS in Objective-C++. I'm mainly using Objective-C for the UI and other Apple APIs, and C++ for internal audio processing and other information handling. I was wondering about the drawbacks of mixing Objective-C and C++ freely.
Of course, mixing two object models has its inherent limitations and potential for messiness and confusion. I'm more curious about how using Objective-C++ will affect the compilation process, syntactic pitfalls I might run into, problems with readability and how I might avoid those, etc. I'm interested to hear what your experiences with Objective-C++ have been like and tips you might have for approaching this.
Swift is easier to read and easier to learn than Objective-C. Objective-C is over thirty years old, and that means it has a more clunky syntax. Swift streamlines code and more closely resembles readable English, similar to languages like C#, C++, JavaScript, Java, and Python.
Objective C messaging is slow relative to accessing lots of small data type elements (every pixel in a large image bitmap or every audio sample in an entire song) inside innermost loops. Objective C is really fast relative to doing anything at the rate of UI or even display refresh events.
Swift is a general-purpose, high-level programming language which is highly concerned about safety, performance. Objective C is an general purpose language which is considered as superset of C language it was designed in an aim of providing object-oriented capabilities. 02.
Community & supportAlthough Objective-C is still supported by Apple, it has never been an open-source language.
ObjC++ is extremely powerful - you can select and mix the features you need for your problems and interface with C, ObjC, and C++ all at the same time. I've been using it for many years. There are of course, a few caveats, and it's good to be aware of them so you can minimize the issues you might encounter:
Compilation
The compilation times are much higher than ObjC or C++ when you begin creating nontrivial programs.
There are a few common approaches to declaring your C++ types in ObjC types:
I'll just gloss over this, as it is inferred from the OP that you are familiar with both languages. As well, this is one of the more publicly written about introductory topics on ObjC++.
Given the C++ type:
class t_thing { public: int a; };
You have a number of ways to declare your ivars:
Opaque type:
@interface MONClass : NSObject { void* thing; } @end
This should be avoided. It's not good to erase type safety. The two forward options will introduce type safety.
This variant is compatible with ObjC translations.
Forward Declaration:
class t_thing;
@interface MONClass : NSObject { t_thing* thing; } @end
This is better than an opaque type, but the smart pointer is even better - pretty obvious if you are used to writing modern C++.
This variant is compatible with ObjC translations as long as your C++ types are in the global namespace.
Forward Declaration using smart pointers:
class t_thing;
@interface MONClass : NSObject { t_smart_pointer<t_thing> thing; } @end
This one is the best if you intend to set up translation firewalls (e.g. use PIMPL and forwards to reduce dependencies). As well, the ObjC object is already going through locking and allocations, so it's not a bad point to allocate a C++ type. If you have several declarations, you may prefer to create a wrapper type for your implementation to reduce individual allocations.
This variant is not compatible with ObjC translations.
This is a good time to remind you that there is a compiler option with ObjC++ that you should enable: GCC_OBJC_CALL_CXX_CDTORS
. What happens when this flag is set? The compiler produces hidden objc methods which invoke your C++ ivars' constructors and destructors. If you use GCC_OBJC_CALL_CXX_CDTORS
your C++ ivars must be default constructible. If you do not enable this flag, you must manually construct and destruct your ivars perfectly - if you construct it twice or do not override an initializer of the subclass, then you are facing UB.
By value:
#include "thing.hpp"
@interface MONClass : NSObject { t_thing thing; } @end
Highest dependency. This is (in general) the route I chose, and I have some regrets about that. I've just moved things over to use more C++ and use composition with smart pointers (outlined above) to reduce dependency.
This variant is not compatible with ObjC translations.
One other thing about the modern ObjC compilers: The compiler lays out your C++ types' ivars/structure in the binary. Believe it or not, this can consume a lot of binary space.
The point here is that there are multiple forms the program can take. You can mix these techniques to reduce dependency, and this is one of the best places to introduce dependency firewalls because ObjC is very dynamic (its methods must be exported in one translation), and object creation requires allocations, locks, introduction into the reference counting system - initialization time for a single object is already relatively high, and the implementation will always be hidden.
If much of your program is still in ObjC and you want to keep it that way, then you will need to resort to forwards of types which are declared in the global namespace or opaque base types which you vend specializations through an object factory. Personally, I just use so much C++ that this was not an ideal option, and wrapping implementations in global types quickly became tiresome.
Meanwhile, since compilation times are high, the inverse is true: If you can keep significant portions of your implementation as C++, then you will save a lot of compilation time. For this reason and ARC (below) you can gain a lot by keeping your primitive Apple types as CF types where possible, so you can continue building C++ programs without the ObjC extensions.
Syntax
I rarely have problems but I keep my C++ classes quite strict:
If you're awesome at C++, then you could avoid this problem, but I prefer the compiler to catch silly mistakes I make.
One evident problem is C++ scope resolution within an ObjC message send. This requires a space:
[obj setValue:::func(a)]; // << bad
[obj setValue: ::func(a)]; // << good
Readability
One problem I have encountered is that I have never found a code formatter that supports the ObjC++ syntax well.
ObjC Messaging
ObjC Messaging and return by value: You need to check before messaging nil
when returning C++ types by value. If the object you message is nil
, then the result will be zeroed memory on the modern runtimes (x86_64 and iOS). If you use that instance, it is undefined behaviour.
ObjC Messaging and return by reference: You need to check before messaging nil
when returning C++ types by reference. If the object you message is nil
, then the result will be undefined behaviour (reference to 0/NULL
).
To overcome the ObjC Messaging issues, I typically use a form like this:
- (bool)selector:(std::string&)outValue;
where the return value is false for some internal error, and true for success.
then you can safely write:
if (![obj selector:outString]) { /* bail here */ }
Miscellanea
ARC Compatibility: ObjC++ is not good for ARC. The primary reason is that ARC does not follow through mixed object models. Example: If you try to put a ObjC member into a C++ type, the compiler will reject the program under ARC. This is not really an issue because MRC is dead simple with ObjC++ (assuming you also use SBRM), but it may be a concern for the lifetime of your program.
Synthesized Properties: You will have to define your properties for C++ types.
External Tools: Beyond Xcode's toolset, there are few programs that handle or recognize ObjC++ well. Text editors, IDEs, utilities.
Apple's Tools: Within Xcode's utilities, Xcode's support for ObjC++ is a bit low. Refactoring (unavailable), navigation (improved with clang parser), outlining (is rather primitive), ObjC++ can disrupt IB's utilities, project upgrading is often not supported.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With