使用C++封装Objective-C的方法

12

我希望能够在OS X上从C++项目中调用和使用Objective-C类。虽然我们需要一定时间来过渡到完全使用Objective-C,但现在是开始迈出这一步的时候了。

如何实现这个目标?是否可以提供一个示例来解释一下呢?


你是在尝试调用Foundation(非UI)对象还是AppKit(UI)对象?你是否在main()中调用了NSApplicationMain()或者正在尝试避免它? - Rob Napier
2
可能是从C++方法调用Objective-C方法?的重复问题。 - Matt K
在SO上还有一个objective-c++标签,你也应该去看看。http://stackoverflow.com/questions/tagged/objective-c++ - Matt K
@Rob Napier - 包括 Foundation 和 App Kit。文件系统调用和打开文件、保存、警报等对话框。 - JTO
4个回答

9

Objective-C++是C++的超集,就像Objective-C是C的超集一样。它被OS X上的gcc和clang编译器支持,并允许您从C++中实例化和调用Objective-C对象和方法。只要在C++模块的实现中隐藏Objective-C头文件导入和类型,它就不会影响到您的“纯”C++代码。

.mm是Objective-C++的默认扩展名。Xcode将自动处理正确的事情。

例如,以下C++类返回自1970年1月1日以来的秒数:

//MyClass.h

class MyClass
{
  public:
    double secondsSince1970();
};

//MyClass.mm

#include "MyClass.h"
#import <Foundation/Foundation.h>

double MyClass::secondsSince1970()
{
  return [[NSDate date] timeIntervalSince1970];
}

//Client.cpp

...
MyClass c;
double seconds = c.secondsSince1970();

你很快会发现,Objective-C++ 的编译速度比 C++ 还要慢,但正如上面所示,将其用法隔离到少量的桥接类中是相对容易的。

9

我认为是Phil Jordan提出了最好的C++包装Obj-C的公式。 然而,我认为后者,即用Obj-C包装的C ++更有用。 我会在下面解释原因。

用C ++包装Objective-C对象

Person.h-Obj-C头文件

@interface Person : NSObject
@property (copy, nonatomic) NSString *name;
@end

PersonImpl.h - C++头文件

namespace people {
    struct PersonImpl;

    class Person
    {
    public:
        Person();
        virtual ~Person();

        std::string name();
        void setName(std::string name);

    private:
        PersonImpl *impl;
    };
}

Person.mm - Obj-C++实现

#import "Person.h"

#import "PersonImpl.h"

namespace people {
   struct PersonImpl
   {
      Person *wrapped;
   };

   Person::Person() :
   impl(new PersonImpl())
   {
      impl->wrapped = [[Person alloc] init];
   }

   Person::~Person()
   {
      if (impl) {
         [impl->wrapped release]; // You should turn off ARC for this file. 
                                  // -fno-objc-arc in Build Phases->Compile->File options
      }

      delete impl;
   }

   std::string Person::name()
   {
      return std::string([impl->wrapped UTF8String]); 
   }

   void Person::setName(std::string name)
   {
      [impl->wrapped setName:[NSString stringWithUTF8String:name.c_str()]];
   }
}

@implementation Person
@end

使用Objective-C封装C++对象

我经常发现真正的问题不是让C++与Obj-C代码交互,而是在两者之间来回切换时会变得很丑陋。想象一下一个需要具有仅限于C++的项目,但基本对象细节是在Obj-C领域填写的对象。在这种情况下,我会用C++编写对象,然后使其能够在Obj-C中运行。

Person.h - C++头文件

namespace people
{
   struct PersonImpl;

   class Person
   {
   public:
      Person();
      Person(Person &otherPerson);
      ~Person();
      std:string name;

   private:
      PersonImpl *impl;
   }
}

Person.cpp - C++ 实现

namespace people
{
   struct PersonImpl
   {
       // I'll assume something interesting will happen here.
   };

   Person::Person() :
   impl(new PersonImpl())
   {
   }

   Person::Person(Person &otherPerson) :
   impl(new PersonImpl()),
   name(otherPerson.name)
   {
   }

   ~Person()
   {
      delete impl;
   }
}

Person.h - Obj-C头文件

@interface Person : NSObject
@property (unsafe_unretained, nonatomic, readonly) void *impl;
@property (copy, nonatomic) NSString *name;
@end

Person.mm - Obj-C++实现

@interface Person ()
@property (unsafe_unretained, nonatomic) std::shared_ptr<people::Person> impl;
@end

@implementation Person

- (instancetype)init
{
   self = [super init];
   if (self) {
      self.impl = std::shared_ptr<people::Person>(new people::Person());
   }

   return self;
}

- (instancetype)initWithPerson:(void *)person
{
   self = [super init];
   if (self) {
      people::Person *otherPerson = static_cast<people::Person *>(person);
      self.impl = std::shared_ptr<people::Person>(new people::Person(*otherPerson));
   }

   return self;
}

- (void)dealloc
{
   // If you prefer manual memory management
   // delete impl;
}

- (void *)impl
{
   return static_cast<void *>(self.impl.get());
}

- (NSString *)name
{
   return [NSString stringWithUTF8String:self.impl->name.c_str()];
}

- (void)setName:(NSString *)name
{
   self.impl->name = std::string([name UTF8String]);
}

@end

关于void *

一旦你踏入C++的领域,如果你想避免整个项目中都是.mm文件,你就会感到某些痛苦。所以,如果你认为不需要将C++对象取回或使用C++对象重构Obj-C对象,那么可以删除该代码。但需要注意的是,一旦通过void *方法从Obj-C代码中删除了Person实例,则必须使用复制构造函数创建自己的副本,否则指针将变为无效。


3

首先将文件从*.m重命名为*.mm,这样就能得到Objective-C++

我没有尝试过,所以这只是猜测(今晚我会尝试):

由于所有Objective-C++对象(即引用计数的对象)都是通过指针进行控制的,因此您可以为共享指针编写特殊的析构函数。

template<typename T>
struct ObjCDestruct
{
    void operator()(T* obj)
    {
        [obj release];
    }
};

现在您可以将Objective-C对象存放在boost::shared_ptr中。

// FuncFile.M
//
int func()
{
    boost::shared_ptr<MyX, ObjCDestruct<MyX> >  data([[MyX alloc] init]);

    [data.get() doAction1:@"HI"];
}

如果你想要更加出色,你可以为is_ObjC_obj子类化type_traits,并在其上进行专门化 ;) - Richard

0

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