今天我感到无聊,决定尝试 C++/Obj-C 的插值操作,结果发现了一种非常有趣的设置方法。
@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();
};
正如您所看到的,这个界面非常直观,易于理解。我们创建了两个(几乎)相同的接口,一个用于C++对象,另一个用于Obj-C协议。
现在,我找到了一种实现这个的方法,但是准备好了吗,这会变得很丑陋:
// 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;
}
我将简要总结一下这段代码的作用:
- 分配一个类对(Class Pair)
- 将所有类方法和实例方法添加到该对象中
- 注册类对
现在,正如您所看到的,这并不是非常灵活的,但它确实能够工作,并且是双向的。
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;
您也可以使用new NSCPPObj(15)
来创建对象,但记得删除它!
显然,这在ARC或非ARC环境下都可以工作,但ARC需要进行一些额外的桥接转换。
所以,我来到了真正的问题:
这种设计结构有什么优缺点? 我能够列出一些:
优点:
- C++中的运算符重载
- ObjC中的动态方法绑定
- 可以按照C++或ObjC方式构建
缺点:
- 实现难以阅读
- 必须为添加到接口的每个C++实现添加选择器和绑定
- 不能直接引用类对象
所以,在所有这些之后,您会推荐在应用程序中采用这种设计结构吗?原因是什么。
id
的隐式转换。我不是在赞扬那个技术,而是指出它可能是解决特定问题的更简单的解决方案。 - Jonathan Sterling