So, I was bored today, and decide to mess with C++/Obj-C interpolation, and I found a way to create a very interesting setup.
@protocol NSCPPObj <NSObject> -(id) init; -(id) initWithInt:(int) value; -(int) somethingThatReturnsAValue; -(void) doSomething; @end class NSCPPObj : objc_object { public: static Class cls(); int iVar; NSCPPObj(); NSCPPObj(int); int somethingThatReturnsAValue(); void doSomething(); };
As you can see, the interface is quite straightforward, and easy to understand. We create two (almost) identical interfaces, one for a C++ object, and another for a Obj-C protocol.
Now, I found a way to implement this, but brace yourself, this gets ugly:
// NSCPPObj.mm #import <objc/runtime.h> #import <iostream> #import "NSCPPObject.h" Class NSCPPObj_class = nil; __attribute__((constructor)) static void initialize() { NSCPPObj_class = objc_allocateClassPair([NSObject class], "NSCPPObj", 0); class_addMethod(NSCPPObj_class->isa, @selector(alloc), imp_implementationWithBlock(^(id self) { return class_createInstance(NSCPPObj_class, sizeof(struct NSCPPObj)); }), "@@:"); class_addMethod(NSCPPObj_class, @selector(init), imp_implementationWithBlock(^(id self) { return self; }), "@@:"); class_addMethod(NSCPPObj_class, @selector(initWithInt:), imp_implementationWithBlock(^(id self, int value) { ((struct NSCPPObj *) self)->iVar = value; return self; }), "@@:i"); class_addMethod(NSCPPObj_class, @selector(doSomething), imp_implementationWithBlock(^(id self) { ((struct NSCPPObj *) self)->doSomething(); }), "v@:"); class_addMethod(NSCPPObj_class, @selector(somethingThatReturnsAValue), imp_implementationWithBlock(^(id self) { return ((struct NSCPPObj *) self)->somethingThatReturnsAValue(); }), "i@:"); objc_registerClassPair(NSCPPObj_class); } Class NSCPPObj::cls() { return NSCPPObj_class; } NSCPPObj::NSCPPObj() { this->isa = NSCPPObj_class; [((id<NSCPPObj>) this) init]; } NSCPPObj::NSCPPObj(int value) { this->isa = NSCPPObj_class; [((id<NSCPPObj>) this) initWithInt:value]; } void NSCPPObj::doSomething() { std::cout << "Value Is: " << [((id<NSCPPObj>) this) somethingThatReturnsAValue] << std::endl; } int NSCPPObj::somethingThatReturnsAValue() { return iVar; }
I'll summarize what this does:
Now, as you can see, this isn't very flexible, but it does work, and it's a two-way street:
id<NSCPPObj> obj = [[NSCPPObj::cls() alloc] initWithInt:15]; [obj doSomething]; NSLog(@"%i", [obj somethingThatReturnsAValue]); NSLog(@"%@", obj); NSCPPObj *objAsCPP = (__bridge NSCPPObj *) obj; objAsCPP->doSomething(); std::cout << objAsCPP->somethingThatReturnsAValue() << std::endl;
You can also create the object by using new NSCPPObj(15)
, but remember to delete it! Obviously, this can work in a ARC or non-ARC environment, but ARC requires a few extra bridged casts.
So, I come to the real question:
What are the pros/cons of this design structure? I can list a few off of the top of my head:
Pros:
Cons:
So, after all that, would you recommend this design structure in an application? and why.
Objective-C is an object-oriented programming language that is a superset of C, as the name of the language might reveal. This means that any valid C program will compile with an Objective-C compiler. It derives all its non-object oriented syntax from C and its object oriented syntax from SmallTalk.
Calling a C function is about 3-6 times faster than calling an Obj-C method, so how can they be equally fast? They can only be equally fast if you never call methods in Obj-C but that some defies the purpose of using Obj-C.
So, after all that, would you recommend this design structure in an application? and why.
No.
It is a really nice bit of code; I particularly like the use of imp_implementationWithBlock() (but I admit I might be partial to that particular feature of the runtime ;). And, of course, explorations like this are always an incredibly valuable learning tool.
The issue, in the context of "real world paying project" use, is that you are effectively creating a relatively generic bridge that will then have to have specific bridges at either end to interface with either typical C++ libraries or typical Objective-C APIs/libraries. To put it another way, you have effectively created a new runtime derived from an amalgamation of two existing runtimes.
And, as you point out in the Cons, you pretty much have to touch, wrap, modify and/or debug a shim on top of every C++ class you want to bring into this pattern.
In working with quite a bit of Objective-C++ code over the last 20+ years, a bridge like this is generally more trouble than it is worth. You would likely be better off -- spend less time writing and debugging code -- creating simple Objective-C wrappers around the C++ (or C, frankly) APIs that can then be integrated with and consumed by the targeted system's Objective-C frameworks.
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