LoginSignup
12
11

More than 5 years have passed since last update.

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

Posted at

こんにちは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
と同じ記事を書いています。

12
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
11