こんにちはnasustです。
Objective-C++とは、Objective-CとC++を混ぜる技術です。
どんな場合に使用するのかは以下の場合があると思います。
- C++のオープンソースを利用する
- 社内の共通プラットフォームライブラリがC++
- パフォーマンス向上の為、一部C++で記述する
特にC++のオープンソースは、Objective-Cより、すごく多いです。求める用途のObjective-Cのオープンソースが無い場合は、C++のオープンソースを利用することがあります。
では、Objective-CとC++を混ぜるということは、どういったことなのか。それはObjective-Cのメソッドの中に、C++のクラスのインスタンスを生成して、普通にメソッドを呼ぶことができます。これは分かりやすいと思います。
分かりにくいのは、ARC+Blocks環境です。Blocksを使用した時にC++のクラスの寿命はどうなるのか、今ひとつピンときません。今回は、サンプルコードと共に調査したいと思います。
いきなりですが、登場クラスを全て紹介します。
using namespace std;
class TestClass {
public:
TestClass(const char *name) : m_name(name) {
cout << m_name << "::Constructor: " << this << endl;
}
TestClass(const TestClass & obj){
this->m_name = obj.m_name;
cout << m_name << "::CopyConstructor: " << this << endl;
}
virtual ~TestClass() {
cout << m_name << "::Destructor: " << this << endl;
}
TestClass & operator=( const TestClass & obj ){
this->m_name = obj.m_name;
cout << m_name << "::operator=: " << this << endl;
return *this;
}
void hello() const {
cout << m_name << "::hello: " << this << endl;
}
private:
string m_name;
};
寿命を検証するC++のクラスです。
@implementation ObjectInCppClass {
shared_ptr<TestClass> hoge;
}
- (id)init {
self = [super init];
if (self) {
hoge.reset(new TestClass("ObjectInCpp"));
}
return self;
}
- (id)initWithName:(NSString *)name {
self = [super init];
if (self) {
hoge.reset(new TestClass([name cStringUsingEncoding:NSUTF8StringEncoding]));
}
return self;
}
- (void)hello {
hoge->hello();
}
TestClassをラップしたObjective-Cのクラスです。
@interface RootViewController ()
@property(nonatomic, strong) UIButton *constructorButton;
@property(nonatomic, strong) UIButton *blockCaptureButton;
@property(nonatomic, strong) UIButton *objectInCppButton;
@property(nonatomic, strong) UIButton *objectInCppBlockButton;
@end
@implementation RootViewController {
}
- (void)loadView {
[super loadView];
self.constructorButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[self.constructorButton setTitle:@"Constructor" forState:UIControlStateNormal];
self.blockCaptureButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[self.blockCaptureButton setTitle:@"BlockCapture" forState:UIControlStateNormal];
self.objectInCppButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[self.objectInCppButton setTitle:@"ObjectInCpp" forState:UIControlStateNormal];
self.objectInCppBlockButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[self.objectInCppBlockButton setTitle:@"ObjectInCppBlock" forState:UIControlStateNormal];
[self.view addSubview:self.constructorButton];
[self.view addSubview:self.blockCaptureButton];
[self.view addSubview:self.objectInCppButton];
[self.view addSubview:self.objectInCppBlockButton];
}
- (void)viewDidLoad {
[super viewDidLoad];
[self.constructorButton addEventHandler:^(id sender) {
TestClass testClass("Scope");
} forControlEvents:UIControlEventTouchUpInside];
TestClass testClass("BlockCapture");
[self.blockCaptureButton addEventHandler:^(id sender) {
testClass.hello();
} forControlEvents:UIControlEventTouchUpInside];
[self.objectInCppButton addEventHandler:^(id sender) {
ObjectInCppClass *objectInCppClass = [ObjectInCppClass alloc].init;
} forControlEvents:UIControlEventTouchUpInside];
ObjectInCppClass *objectInCppBlockClass = [[ObjectInCppClass alloc] initWithName:@"ObjectInCppBlock"];
[self.objectInCppBlockButton addEventHandler:^(id sender) {
[objectInCppBlockClass hello];
} forControlEvents:UIControlEventTouchUpInside];
}
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
self.constructorButton.width = [UIScreen currentWidth];
self.constructorButton.height = 44;
self.constructorButton.x = 0;
self.constructorButton.y = 20;
self.blockCaptureButton.width = [UIScreen currentWidth];
self.blockCaptureButton.height = 44;
self.blockCaptureButton.x = 0;
self.blockCaptureButton.y = self.constructorButton.bottom;
self.objectInCppButton.width = [UIScreen currentWidth];
self.objectInCppButton.height = 44;
self.objectInCppButton.x = 0;
self.objectInCppButton.y = self.blockCaptureButton.bottom;
self.objectInCppBlockButton.width = [UIScreen currentWidth];
self.objectInCppBlockButton.height = 44;
self.objectInCppBlockButton.x = 0;
self.objectInCppBlockButton.y = self.objectInCppButton.bottom;
}
@end
Blokskitを使用してUIButtonにBloksをイベントハンドラーとして設定しています。
このブロックで実際に動かしながら、C++のクラスの寿命を検証したいと思います。
viewDidLoad実行時
コンソールには以下のように表示されます。
BlockCapture::Constructor: 0xbfffca20
BlockCapture::CopyConstructor: 0xbfffca0c
BlockCapture::CopyConstructor: 0xb995c04
ObjectInCppBlock::Constructor: 0xb9ab490
BlockCapture::Destructor: 0xbfffca0c
BlockCapture::Destructor: 0xbfffca20
スタックに置いたC++のBlockCaptureの名前のクラスがキャプチャされています。そしてデストラクタが2回発生しています。2回発生しているのはキャプチャ時にインスタンスが2回コピーされている為です。最後に0xb995c04のインスタンスがBloksにキャプチャされます。
BlockCaptureのブロック
"BlockCapture"のラベルのボタンを押すとコンソールには以下のように表示されます。
BlockCapture::hello: 0xb995c04
キャプチャされたインスタンスが実行されています。C++のインスタンスをキャプチャする場合はshared_ptrを使用した方が安全であるし、無駄にコピーされるのでパフォーマンスが良さそうです。
Scopeのブロック
"Scope"のラベルのボタンを押すとコンソースには以下のように表示されます。
Scope::Constructor: 0xbfffd730
Scope::Destructor: 0xbfffd730
これは分かりやすいですね。スコープから抜けるとインスタンスが破棄されます。
ObjectInCppのブロック
"ObjectInCpp"のラベルのボタンを押すとコンソースには以下のように表示されます。
ObjectInCpp::Constructor: 0xb8a72b0
ObjectInCpp::Destructor: 0xb8a72b0
これも分かりやすいですね。スコープから抜けるとObjective-Cのクラスが破棄されて、メンバにあるC++のクラスも破棄されています。
ObjectInCppBlockのブロック
"ObjectInCppBlock"のラベルのボタンを押すとコンソースには以下のように表示されます。
ObjectInCppBlock::hello: 0xb9ab490
Objective-Cのクラスでラッピングしている為、コピーもされずにC++のhelloメソッドが実行されています。そもそもObjective-CのクラスのメンバにはC++で引数無しのデフォルトコンストラクタ以外書けないので、shared_ptrかポインタで置くしかありません。ですのでコピーされないのです。
以上の検証でBloks+ARC環境でC++のインスタンスの寿命が理解できたでしょうか?
オープンソースを利用すると、Objective-C++を利用する場合がありますので、理解を深めておくと良さそうさです。
GitHubにサンプルプロジェクトを置いています。
シュミレーターで検証してみてください。要Cocoapods.
昔はまともに動きませんでしたが、今は普通に動きますね。
http://showrtpath.hatenablog.com/entry/2013/12/23/153906
と同じ記事を書いています。