- 拡張子が .mm の Objective-C++ ソースファイルには C++ のコードを書けるが、拡張子が .cpp の C++ ソースファイルには Objective-C のコードを書けないので何とかしたい。
- マルチプラットフォームのためソースファイル名は .cpp にしておきたいが、必要な時にだけ Cocoa Touch の機能を使いたい。
.cpp ファイルを Objective-C++ としてコンパイルさせる方法
ファイル単位で指定
.cpp ファイルを選択後、File inspector で Type を Objective-C++ Source に変える。ソース内にプラグマとかで指示できると分かりやすいんだけど、そういうのはないぽい。
全てのソースをObjective-C++扱いでコンパイルさせる
ターゲットまたはプロジェクトの Build Settings の Compile Source As を According to File Type から Objective-C++ に変える。.cpp が大量にあるなら便利だけど、遅いとかデカくなるとかなんか罠がありそうで、あんまやりたくない。
.cpp ファイルはあくまで C++ としてコンパイルさせたい場合
チームや宗教的な都合上、ファイル設定やコンパイル設定を変更する事が許されない場合は、クラスの宣言は純粋な C++ として書いて、Objective-C の機能を使うメソッドだけ .mm ファイルに隔離して実装するという手もある。いろいろ鬱陶しいけど。
MyClass.h <- 純粋なC++クラスの宣言
MyClass.cpp <- C++ だけで書けるメソッドはこっちに実装
MyClass.mm <- Objective-C を使いたいメソッドはこっちに隔離
ObjCMethods.mm <- .mmが無駄に増えるのが嫌なら Objective-C の部分だけ寄せ集めたり(暴挙)
Objective-C のオブジェクトを C++ のクラスのメソッドに渡したり、クラスメンバとして保持したい
Objective-C のオブジェクトも基本ポインタでやりとりしてるので、以下みたいにヘッダで前方宣言しておけばいけるんじゃね?と思ったけど、そもそも Objective-C のクラスは class でも struct でもないので純粋な C++ では前方宣言できない。
// 内容は教えないが MyClassA というクラスがある事だけ宣言
class MyClassA;
class MyClassB
{
private:
// 内容が分からないので実体は作れないが、ポインタは保持できる
MyClassA* mClassA;
public:
// メソッド引数にもできる
void
SetClassA(
MyClassA* classA);
};
// Objective-C のソース内であれば、以下のように Objective-C の
// クラスも前方宣言できるらしいが、C++では当然エラー。
@class MyObjCClass;
てことで一見無理そうなんだけど、どうしても C++ のクラス内に ViewController
のポインタを保持したいケースがあったので、足掻いた結果以下のようになった。
#import <UIKit/UIKit.h>
// C++クラスにViewControllerを渡すためのラッパー
struct VCWrapper
{
// weak にしないと循環参照になる。weak 超重要!
__weak UIViewController* vc;
};
#import "MyViewController.h"
#import "VCWrapper.h"
@implementation MyViewController
{
// ViewController 内に C++ のクラスを保持して、実処理はそちらでやる
MyCppClass mCppClass;
VCWrapper mVcWrapper;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Objective-Cのクラスは直接渡せないので、ラッパーに包んで渡す。
// ラッパーは使い終わるまで保持しとかないといけない。
mVcWrapper.vc = self;
mCppClass.SetOwnerViewController( &mVcWrapper );
}
@end
// このヘッダは純粋なC++なので、他の.cppソースからインクルードしても問題ない
// VCWrapper の中身は他のファイルには隠蔽
struct VCWrapper;
class MyCppClass
{
private:
// ViewController はラッパーのポインタで保持
VCWrapper* mOwnerVC;
public:
void
SetOwnerViewController(
VCWrapper* vc)
{ mOwnerVC = vc; }
// 例: C++ ソースからViewControllerを閉じる
void
DismissViewController();
};
// このソースは .cpp だが、File inspector で Objective-C++ としてコンパイルさせる
// もしくは拡張子を .mm にする
#import "MyCppClass.h"
#import "VCWrapper.h"
// 例: C++ ソースからViewControllerを閉じる
void
MyCppClass::DismissViewController()
{
// このファイル内では ViewController のメソッドも呼び出し可能
[mOwnerVC->vc dismissViewControllerAnimated:true completion:nil];
}
もっといい方法ないのかね。回りくどいだけで大したことはしてないんだけど、ラッパー内に保持するメンバを __weak
にするのだけは忘れないように。処分されなくなるよ。
Objective-C や C++ でのコンパイルを判別するマクロ
【2017/11/21追記】
C++ と Objective-C の両方からインクルードするヘッダ内に Objective-C のコードを書いたり import したい場合は以下の定義済みマクロで判定できる。
#ifdef __OBJC__
// Objective-C または Objective-C++ としてコンパイルした場合のみ有効。
// C++ からもインクルードするヘッダ内に Objective-C のコードを書いたり
// Foundation.h や UIKit.h などを import するならこの中で。
#endif
#ifdef __cplusplus
// Objective-C++ または C++ としてコンパイルした場合のみ有効。
// 例えば Swift との bridging header にも含めたいヘッダには
// C++ のコードは書けないので、これで無効化しないといけない。
#endif