Objective-C の Object を C++ で扱う (smart pointer)

iOS/OSX の API の多くは Objective-C の Interface となっています。
他のプラットフォームとのコード共有を考えるならば、
アプリケーション側はできるだけ普通の C/C++ で書きたいと思うかもしれません。
この場合 System の API 呼び出し部分を分離して、何らかの Wrapper 経由で
アクセスすることになります。

Applicaton     *.cpp : C++
Library header *.h   : C++ / Objective-C++
Library source *.mm  : Objective-C++

Wrapper Library のヘッダは C++ と Objective-C++ で共有されることになるので、
Objective-C の Object をそのまま記述することが出来ません。
ヘッダとソースで実装を分離して隠蔽すべきなのでしょうが、
扱う Class や Object が増えて結構手間がかかっていました。

おそらく一番簡単で確実な方法は、iOS/OSX の場合だけアプリケーション側の
.cpp (.cc) を Objective-C++ とみなしてコンパイルすることでしょう。

ですが、どうしても純粋な C++ としてコンパイルしたかったので、
C++ で Objective-C の Object を所有できるようにしてみました。

// IDPointer.h
class IDPointer {
    void*  iPointer;
public:
    IDPointer() : iPointer( NULL )
    {
    }
    ~IDPointer()
    {
        Clear();
    }
    void Clear();
#if __OBJC__
    void SetID( id obj )
    {
        Clear();
        iPointer= (__bridge_retained void*)obj;
    }
    id GetID() const
    {
        return  (__bridge id)iPointer;
    }
#endif
};
// IDPointer.mm
#import  "IDPointer.h"

void IDPointer::Clear()
{
    id  obj= (__brdige_transfer id)iPointer;
    obj= NULL;
    iPointer= NULL;
}

一見うまく動作するように見えますが、dealloc のタイミングが意図したものに
ならないことがあります。

void idpointer_test()
{
    @autoreleasepool {
      TempClass*  t0= [[TempClass alloc] init];
      IDPointer  ptr;
      ptr.SetID( t0 );

      TempClass*  p0= ptr.GetID();

      t0= NULL;
      p0= NULL;
      ptr.Clear();
      // --- (1)
    }
    // --- (2)
}

上の例は (1) ではなく (2) のタイミングで TempClass が dealloc されます。
原因は GetID() が id を返しているためで、ARC により autoreleasepool への
登録が行われているようです。
weak なポインタを返す場合、所有者の生存期間をコンパイラが保証できないため
autoreleasepool に委ねているものと思われます。
実際には autoreleasepool に入って欲しくないケースもあります。

IDPointer& operator=( const IDPointer& src )
{
    SetID( src.GetID() );
    return  *this;
}

例えば上のコードは SetID() が即座に retain しているため、
生存期間を考慮する必要がないのですが autoreleasepool に入ります。

void* のまま受け取ってから受け取り側が __bridge cast するか、または
__attribute__((ns_returns_retained)) をつけると autoreleasepool に
含まれないことがわかりました。

// IDPointer.h
class IDPointer {
    void*  iPointer;
public:
    IDPointer() : iPointer( NULL )
    {
    }
    ~IDPointer()
    {
        Clear();
    }
    IDPointer( const IDPointer& src );
    IDPointer& operator=( const IDPointer& src );
    void Clear();
#if __OBJC__
    explicit IDPointer( id obj )
    {
        iPointer= (__bridge_retained void*)obj;
    }
    void SetID( id obj )
    {
        Clear();
        iPointer= (__bridge_retained void*)obj;
    }
    __attribute__((ns_returns_retained)) id GetID() const
    {
        return  (__bridge id)iPointer;
    }
    IDPointer& operator=( id obj )
    {
        SetID( obj );
	return  *this;
    }
#endif
};
// IDPointer.mm
#import  "IDPointer.h"

IDPointer::IDPointer( const IDPointer& src )
{
    iPointer= (__bridge_retained void*)src.GetID();
}
IDPointer& IDPointer::operator=( const IDPointer& src )
{
    SetID( src.GetID() );
    return  *this;
}
void IDPointer::Clear()
{
    id  obj= (__brdige_transfer id)iPointer;
    obj= NULL;
    iPointer= NULL;
}

↑の場合は GetID() しても autoreleasepool に入ることがありません。

これで C++ 側でも比較的安全に Objective-C Object の所有や受け渡しを
行うことが出来ます。
Objective-C の Object の instance は ARC で管理されていますが、
C++ からは smart pointer (shared_ptr) として見えることになります。

使用例

// SampleAPI.h
class PlayerData : public IDPointer {
};
class Player {
    IDPointer  PlayerInstance;
public:
    void Init();
    void CreateData( PlayerData& data );
    void Play( PlayerData& data );
};
// SampleAPI.mm
void Player::Init()
{
    PlayerInstance= [MYPlayer newPlayer];
}

void Player::CreateData( PlayerData& data )
{
    data.SetID( [[MYPlayerData alloc] init] );
}

void Player::Play( PlayerData& data )
{
    id player= PlayerInstance.GetID();
    [player stop];
    [player playWithData:data.GetID()];
}