Blocks+ARC環境でObjective-C++のC++オブジェクトの寿命を検証する

こんにちは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++のクラスの寿命はどうなるのか、今ひとつピンときません。今回は、サンプルコードと共に調査したいと思います。

いきなりですが、登場クラスを全て紹介します。

```cpp
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++のクラスです。


```objc
@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のクラスです。

```objc
@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にサンプルプロジェクトを置いています。

https://github.com/nasust/ObjectiveCppTest

シュミレーターで検証してみてください。要Cocoapods.

昔はまともに動きませんでしたが、今は普通に動きますね。


http://showrtpath.hatenablog.com/entry/2013/12/23/153906
と同じ記事を書いています。