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()];
}