Swiftプロジェクト内でC++を使うには
うるせえ! 私は書きたいことを書く。
SwiftはObjective-Cとの互換性があると聞いた。
であれば、もちろんC, C++も使えるはず、と思っていた。実際に、公式ドキュメントのAbout Swiftの項にも、
Swift is a new programming language for iOS and OS X apps that builds on the best of C and Objective-C, without the constraints of C compatibility.
と書いてある。しかし、C++との互換性があるとは書いていない。「CとObjective-Cの上に構築する」とは書いてあるものの、「C互換の制約に縛られずに」という部分も気になる。
ふと思ったのだが、Objective-Cでは、C++を使う場合、実装ファイル名を.mmにする。
Swiftの場合はどうする?
NOTE
You cannot import C++ code directly into Swift. Instead, create an Objective-C or C wrapper for C++ code.Swift内にC++コードを直接importすることはできません。
代わりに、C++コードをラップするCか、Objective-Cのコードを作成してください。
なんということでしょう。
まーそうかー。考えてみれば、Swiftのコード内にObjective-CとC++のコードがごちゃまぜに混在していたら、いくらなんでもカオスすぎる。
まあ、C++が使えないならSwiftなんて使わないけど、単純にObjective-Cクラスとしてラップすれば良いだけならば、まだ許容できる。じゃあいいか。
・・・
いや、信用できない!
本当にできるか試してみる。
・Xcode 6.0で新規にSwiftプロジェクトを作成。
・「File」→「New」→「File ...」で「Objective-C File」を選択。
・ファイル名を Wrapper.mm とした。
すると、ダイアログが出てきた。
Would you like to configure an Objective-C bridging header?
Yes/No/Cancel
ブリッジングヘッダ? なにそれ?
ああ、うん、もちろんそいつはYesだ! (よく解っていない)
すると、ProjectName-Bridging-Header.hというファイルが自動生成された。
中身はコメントだけで空である。
少し調べてみると、このブリッジングヘッダーなるものは、Swiftコード内で使用する自作Objective-Cクラスをインポートしておくものらしい。このファイル内でimportしたObjective-Cクラスは、Swiftコード内で、Swiftクラスとして使用可能みたい。
// // Use this file to import your target's public headers that you would like to expose to Swift. // #import "Wrapper.h"
なるほどね。
次に、Wrapper.hにC++コードをラップするObjective-Cクラスを作るわけだが、諸事情によりvoid*ポインタを使うことにする。*1
Wrapper.h
#import <Foundation/Foundation.h> @interface MyObjcClass : NSObject { void* _cppObj; // C++クラスのインスタンスを参照するポインタ } -(void)test; // このメソッドをSwiftから呼べて、内部でC++コードが動けばOK、とする @end
次に、.mmファイル内でC++クラスを定義し、それをラップするObjective-Cクラスを実装してみる。
ただ動作できるか確認するだけなので、内容はないよう・・・
Wrapper.mm
#import "Wrapper.h" #include <vector> #include <iostream> class MyCppClass { public: MyCppClass& add(int i) {vct.push_back(i); return *this;} void trace() {std::for_each(vct.begin(), vct.end(), [](int i){std::cout << i << std::endl;});} private: std::vector<int> vct; }; @implementation MyObjcClass -(id)init { self = [super init]; if (self) { _cppObj = new MyCppClass(); MyCppClass& obj = *static_cast<MyCppClass*>(_cppObj); obj.add(10).add(20).add(30); } return self; } -(void)dealloc { delete static_cast<MyCppClass*>(_cppObj); } -(void)test { MyCppClass& obj = *static_cast<MyCppClass*>(_cppObj); obj.trace(); } @end
こうして、今、MyCppClassを内部的に使用する、MyObjcClassが完成したわけだ。
あとはSwift内から、MyObjcClassを使えれば良いということになる。
ViewController.swift
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // ブリッジングヘッダでimportしておくだけで、ここでは特にimportはいらないみたい。 let obj = MyObjcClass() // Objective-Cクラスとして作成したものをインスタンス化 obj.test() // そいつのメソッドを呼んでみる } }
実行してみると、きちんと動作した。
10 20 30
結論として、ある程度のモジュール化は義務付けられることになるだろうが、Swiftを主として使うプロジェクト内においても、局所的にC++を使うことはできる。その場合、C++コードをObjective-Cクラスでラップするか、あるいはCの関数でラップする。*2
まあ、ひとまず安心といったところか。
*1:Objective-Cの時代から、.h内でvectorをincludeしたり、C++クラスの前方宣言をしたり、名前空間を使ったりすると、.mm以外(.m)からimportされたときに問題が起こる。というか、ヘッダファイル内でC++構文を使うという行為は、.cpp, .mmでinclude, importされる場合にしか許容されない。私の場合は、C++を実際に使用するObjective-C++ソースだけを.mmとして局所化することにした。これは即ち、.hを直接、あるいは間接的にimportするすべてのObjective-Cソースを.mmにするようなことはせずに、.h内でC++の構文を使わないようにする、という方針である。今回もそれに従っている。
*2:ここでは記述していないが、ほぼ同様の手順で、Cの関数をブリッジングヘッダー経由で、Swiftから使用できることも確認した。即ち、Swiftソース内で自由にCの命令を書くことはできないが、Swiftから自作C関数を呼び出すことはできる。C++クラスをObjective-Cクラスでラップすると、メッセージ送信のオーバーヘッドを伴うため、場合によってはC関数でラップする方が有効だと思う。具体的なやり方についてはまた別途。