Objective-C - 使用C++桥接的缺点是什么?

25

今天我感到无聊,决定尝试 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;
}

我将简要总结一下这段代码的作用:

  1. 分配一个类对(Class Pair)
  2. 将所有类方法和实例方法添加到该对象中
  3. 注册类对

现在,正如您所看到的,这并不是非常灵活的,但它确实能够工作,并且是双向的。

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需要进行一些额外的桥接转换。

所以,我来到了真正的问题:
这种设计结构有什么优缺点? 我能够列出一些:

优点:

  1. C++中的运算符重载
  2. ObjC中的动态方法绑定
  3. 可以按照C++或ObjC方式构建

缺点:

  1. 实现难以阅读
  2. 必须为添加到接口的每个C++实现添加选择器和绑定
  3. 不能直接引用类对象

所以,在所有这些之后,您会推荐在应用程序中采用这种设计结构吗?原因是什么。


1
我对C++了解太少,无法给出一个很好的答案,但我想这取决于你正在开发的应用程序的具体类型。对于正在移植的现有C++游戏来说,这可能比一个简单的实用程序更有用...而且一个经验丰富的C++程序员可能会比一个深入研究Objective-C的人更加欣赏它。 - lxt
1
投票重新开放。我理解这可能是“不具建设性”的,但这是一个社区网站。我知道你是一名版主,但既然只有你想关闭它,我认为应该重新开放。 - Richard J. Ross III
我不是版主,但我已经标记了这个问题以供审核。它与主题无关。 - N_A
6
这是一个对Mac OS X和iOS开发者有帮助的建设性问题,经常被这个市场中使用数百(千?)个不同C++引擎的开发者所面临。虽然示例本身对于最后的优缺点/具体问题无关紧要,但“在ObjC和C++之间使用通用桥接是否是推荐的模式以及为什么?”这个实际问题非常具体而有价值! - bbum
1
我还想注意到的是,在Objective-C对象周围使用C++包装器可以复制相同的运算符重载。然后,提供对id的隐式转换。我不是在赞扬那个技术,而是指出它可能是解决特定问题的更简单的解决方案。 - Jonathan Sterling
1个回答

23
那么,经过这一切,你会推荐在应用程序中使用这种设计结构吗?为什么不。
这是一段非常好的代码;我特别喜欢使用imp_implementationWithBlock()(但我承认我可能对运行时的这个特定功能有偏见;)。当然,像这样的探索始终是一种非常宝贵的学习工具。
问题在于,在“真实世界付费项目”使用的背景下,您实际上正在创建一个相对通用的桥梁,然后必须在两端都具有特定的桥梁来与典型的C++库或典型的Objective-C API /库接口。换句话说,您实际上创建了一个源自两个现有运行时融合的新运行时。
正如您在缺点中指出的那样,您几乎必须触摸,包装,修改和/或调试每个要引入此模式的C++类的shim。
在过去的20多年里,我一直在处理大量的Objective-C ++代码中,像这样的桥梁通常会带来更多麻烦而不是价值。您可能会更好地花费较少的时间编写和调试代码,创建简单的Objective-C包装器以围绕C ++(或C)API,然后将其与目标系统的Objective-C框架集成和消耗。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接