Objective-C」カテゴリーアーカイブ

C++のクラスにObjective-Cの変数を持たせたい

cocos2d-xでiOS用のアプリを作っていると、C++のクラスにObjective-Cのクラスをメンバ変数として定義したい、というケースがある。

たとえば、Objective-Cで記述されたクラスObjCがあるとする。
これをC++で記述するクラスCppのメンバ変数として定義したい場合。
できることなら、Cpp.hに

#import "ObjC.h"

class Cpp {
    ObjC *_objc;
    ...
};

と記述したいところだけど、ヘッダファイルにはC++とObjective-Cとを混在させられないらしく、できない。
そこで内部クラスというものを利用してみる。

class Cpp {
    class Inner;   // InnerというクラスをCppの内部に宣言し、
    Inner *_inner; // それをメンバ変数とする。
    ...
};

この内部クラスInnerの定義はソースファイルの中に記述できる点がミソだ。

Cpp.mm

#import "ObjC.h"  // 拡張子.mmのソースファイルの中なので、Objective-Cが使える。

// Innerの定義。
class Cpp::Inner
{
    ObjC *_objc;

public:
    Inner() {
        _objc = [ObjC new];
    }
    ~Inner() {
        _objc = nil;
    }

    // ObjCのメソッドを呼び出すためのラッパー
    void doObjcMethod() {
        [_objc doObjcMethod];
    }
};

// Cppの定義
Cpp::Cpp() {
    // Innerクラスのインスタンスを作成
    _inner = new Inner();

    // Innerクラスを介して、ObjCにアクセスする。
    _inner->doObjcMethod();
}
Cpp::~Cpp() {
    delete _inner;
    _inner = NULL;
}

みたいな感じ。

基本的な考え方はこれでいいんじゃないかと思う。
なにか問題などあればご指摘ください。

SKProductsRequestのdelegateでEXC_BAD_ACCESSエラー

iOSのアプリ内課金でプロダクト情報を取得するために、SKProductsRequestのインスタンスにdelegateをセットして、startメソッドを呼び出した。
そして、delegateにセットしたインスタンスでデータを受け取り、諸々処理して、不要になったdelegate用のインスタンスを削除したら、EXC_BAD_ACCESSエラーが発生したというお話。
もちろん、SKProductsRequestのインスタンスのdelegate設定は解除したのに、だ。

-(void)productsRequest:(SKProductsRequest *)request
 didReceiveResponse:(SKProductsResponse *)response {
    ...

    [_productsRequest setDelegate:nil];
    _productsRequest = nil;

    // この通知を受けて、delegate用のインスタンスが削除破棄される
    [[NSNotificationCenter defaultCenter] postNotification:n];
}

といった感じのコード。

推測としては、どうやらSKProductsRequestが-(void)productsRequest:didReceiveResponseをコールした後にも、delegate用のインスタンスにアクセスしようとするんじゃないだろうか、と。
でも、その前にdelegate用のインスタンスが破棄されているので、EXC_BAD_ACCESSエラーが発生しているのではないかと思う。
setDelegate:nilをやっているので、本来であれば、そういったことは起こらないはずなのだけど。

とりあえず、最後の通知部分(インスタンスが削除される部分)を以下のようにしてやることで、エラーを回避することができた。

dispatch_async(dispatch_get_main_queue(), ^{
    [[NSNotificationCenter defaultCenter] postNotification:n];
});

もし何か正しい理由と対策をご存知の方がいらっしゃいましたら、教えてください。
正しいやり方、分かりました。

SKProductsRequestDelegateはSKRequestDelegateを継承していて、このSKRequestDelegateに- (void)requestDidFinish:(SKRequest *)requestというメソッドがある。
リファレンスに

When this method is called, your delegate receives no further communication from the request and can release it.

とある通り、このメソッドを受けてdelegate用インスタンスを破棄すれば良かったのだ。
気づいてみれば、簡単なことだった。