|
HMDT - Special Issue / CoreFoundation の秘密 / Base Services - Toll-free bridge の実現方法 |
- Base Services -
|
|
(sample)
|
NSString* cocoaString; CFIndex length; cocoaString = @"Cocoa String"; /* これは OK! */ length = CFStringGetLength((CFStringRef)cocoaString); |
逆も同じ。CFStringRef のインスタンスを使って、NSString の length メソッドを呼び出せる。
|
(sample)
|
CFStringRef cfString;
int length;
cfString = CFSTR("CoreFoundation String");
/* これも OK! */
length = [(NSString*)cfString length];
|
ここではキャストを付けているけど、warning を気にしなければ、付けなくても OK だ。
こんな感じで使える toll-free bridge。実際に使ってみると、ユーザの立場からはすごく楽。キャストするだけで、2 つのフレームワークをいったり来たりできるなんて、あー楽チン。Mac OS X 10.2 では、以下のクラスが toll-free bridge として提供されているんだ。
CoreFoundation Cocoa CFArray NSArray CFCharacterSet NSCharacterSet CFData NSData CFDate NSDate CFDictinoary NSDictionary CFRunLoopTimer NSTimer CFSet NSSet CFString NSString CFURL NSURL
CFArray や CFDictionary があるのに、Enumerator がないのは片手落ちのような気がするぞ。詳しいことは、Apple の Foundation のリリースノートのドキュメントを見てくれ。
(http://developer.apple.com/techpubs/macosx/ReleaseNotes/Foundation.html) "Toll-free" Bridging with CF Types
![]()
ということで、toll-free bridge がどうやって実装されているのか見てみよう。ここでは CoreFoundation API に Cocoa のオブジェクトが渡されたときにどうなるか、っていうところを見ていく。Toll-free bridge を実装するには、渡されたオブジェクトが CoreFoundation のものか、Cocoa のものかを判別する必要がある。まず、その仕組みを見てみよう。
まずは、CFRuntimeBase 型から。すべての CoreFoundation のオブジェクトは、この型を先頭に持っている。この中に _isa というポインタがあるんだ。
|
CoreFoundation/Base.subproj/CFRuntime.h
|
typedef struct __CFRuntimeBase {
void *_isa;
#if defined(__ppc__)
uint16_t _rc;
uint16_t _info;
...
#endif
} CFRuntimeBase;
|
この _isa ポインタは、Cocoa のオブジェクト(つまり Objective-C の object)にもある。
|
objc.h
|
typedef struct objc_object {
Class isa;
} id*;
typedef struct objc_class {
struct objc_class *isa;
...
} *Class;
|
こんなわけで、両方のオブジェクトの先頭に isa があるんだ。だから、いきなりキャストしあっても、isa なら問題なくアクセスできる、っていう仕組みなんだ。で、この isa ポインタを使って、オブジェクトが CoreFoundation のオブジェクトか Cocoa のオブジェクトかを判別してるんだ。CoreFoundation のオブジェクトの場合、isa は次のような感じで初期化される。
|
CoreFoundation/Base.subproj/CFRuntime.c
|
CFTypeRef _CFRuntimeCreateInstance(
CFAllocatorRef allocator,
CFTypeID typeID,
uint32_t extraBytes,
unsigned char *category)
{
CFRuntimeBase *memory;
...
memory->_isa = __CFISAForTypeID(typeID);
...
|
isa を __CFISAForTypeID() で初期化している。_CCFISAForTypeID() はなにかっていうと、CFTypeID に応じたクラスのポインタを返すんだ。こうして isa が CoreFoundation のオブジェクトと結び付けられるんだ。
(もうちょっと突っ込むと、実は Cocoa には CoreFoundation に対応したクラスが用意されている。たとえば、CFString に対応する NSCFString といった具合に。NSCFString は Objective-C のクラスだ。__CFISAForTypeID() は、このクラスを返す。NSCF** 系のクラスを CoreFoundation は覚えている。それと比較することによって、CoreFoundation のクラスか Objective-C のクラスかが分かるんだ。)
じゃ、実際の関数の中を見てみよう。たとえば CFRetain ではこんな感じ。
|
CoreFoundation/Base.subproj/CFRuntime.c
|
CFTypeRef CFRetain(CFTypeRef cf) {
...
CFTYPE_OBJC_FUNCDISPATCH0(CFTypeRef, cf, "retain");
|
CFTYPE_OBJC_FUNCDISPATHC0() っていうマクロが、toll-free bridge を実現するためのものだ。こいつの定義はこうなっている。ちなみに、引数の数に応じて CFTYPE_OBJC_FUNCDISPATHC1() になったり CFTYPE_OBJC_FUNCDISPATHC2() になったりする。
|
CoreFoundation/Base.subproj/CFInternal.h
|
#define CFTYPE_OBJC_FUNCDISPATCH0(rettype, obj, sel) \
if (CFTYPE_IS_OBJC(obj)) \
{rettype (*func)(void *, SEL) = (void *)__CFSendObjCMsg; \
static SEL s = NULL; if (!s) s = __CFGetObjCSelector(sel); \
return func((void *)obj, s);}
|
このままだと分かりにくいよな。CFRetain() の内部で展開してみよう。
|
(extrancted macro)
|
CFTypeRef CFRetain(CFTypeRef cf) {
...
if (CFTYPE_IS_OBJC(cf)) {
CFTypeRef (*func)(void *, SEL) = (void *)__CFSendObjcMsg;
static SEL s = NULL;
if (!s) s = __CFGetObjCSelector("retain");
return func((void *)cf, s);
}
|
まず func っていう変数に関数ポインタを代入する。関数の型は CFTypeRef (*)(void*, SEL) っていう型。これって、つまり引数なしの Objective-C のメソッドの型っていうことだね。この func に代入されるのは __CFSendObjcMsg っていう関数のポインタだ。次にセレクタを取得する。セレクタは __CFGetObjcSelector() っていう関数にセレクタの名前を渡すことによって取れる。static 変数に代入しているから、キャッシュされることになるんだね。そして、func つまり __CFSendObjcMsge を呼び出す。引数として、オブジェクトとセレクタを渡す。この中で、Objective-C のメソッドが呼び出されるのでしょう。
というわけで、まとめると、toll-free bridge が用意されている関数を呼び出すと、isa ポインタを見て Objective-C のクラスかどうか判別する。Objective-C のクラスだったら、対応するメソッドのセレクタを取り出す。そしてオブジェクトに対してメッセージを送ってやって、関数を抜ける。っていうことになるんだ。
![]()
ということは、実装が二重に用意されていることになる。たとえば、CFStringRef と NSString は、似たような機能を持っているけど実装は別々なんだ。これは二重の手間だよな。もしかすると、NSString の実装の中で、CFStringRef のコードを呼び出しているのかもしれない。これは Cocoa のソースコードを見ないと分からないから、なんともいえない。将来的に 1 つの実装になるようになるのかもしれないし、あくまで CoreFoundation と Cocoa の実装は別物として扱うかもしれない。
|
Home | Link | Download | Back Number | Speciall Issue
|