CoreFoundation の秘密
- String Servcies -
定数文字列のつくり方
CFString での文字列の作り方は、大きく分けて 3 つの方法があるんだ。
という 3 つ。ちなみに、インライン文字列であるかどうかということと、固定文字列であるかどうかということは、必ずしも一致しないので注意。固定文字列だけどインラインじゃない、つまり大きくてインラインにはならない、ということもあるのだ。
ここでは、定数文字列の作り方を見ていくぜ。
定数文字列の作り方
定数文字列を作るには CFSTR() マクロを使う。使い方はこんな感じ。
(sample)
CFStringRef hello = CFSTR("Hello, world.");
という感じで、C の文字列から CFString を作り出すんだ。ここで気をつけなきゃいけないのは、CFSTR() は新しい CFString のインスタンスを作り出すものではない、ということだ。だから、ここで得られた参照を release してはいけない。Apple の説明によると、「これは C で "hello world" ってプログラムの中に書いたようなものなんだ。ほら、こういうときは free しないでしょ?それと似たようなもので、CFSTR() で得られた文字列は release してはいけないんだ」
いささか無理があるような気もする説明だ。で、実際のところはどうなっているかというと、CFSTR() で作られた文字列はキャッシュされてるんだ。最初の 1 個は普通に作られて、キャッシュされる。次に同じものが要求されたときは、キャッシュされているインスタンスを返す。だから、次のソースコードの 2 つの関数は、メモリの使用量という観点からは、同じパフォーマンスのはずだ。
(sample)
void sample0() {
CFStringRef tmp;
tmp = CFSTR("Sample string");
func0(tmp);
func1(tmp);
}
void sample1() {
func0(CFSTR("Sample string"));
func1(CFSTR("Sample string"));
}
一度作られた定数文字列はキャッシュされるから、インスタンスが複数生成されるわけではないんだ。
CFSTR() に関する他の注意点は、こいつは仕様では 7 bit 文字しか受け付けないこと。一応、Mac OS Roman の文字は対応するように実装されているようだけど、将来どうなるかはわからんので使わない方が無難。とうぜん、是非は別にして、日本語も不可。
キャッシュ用のテーブル
文字列をキャッシュしておくテーブルには CFMutableDictionary を使っている。constantStringTable っていう変数だ。
CoreFoundation/String.subproj/CFString.c
static CFMutableDictionaryRef constantStringTable = NULL;
こいつのインスタンスは、CFSTR() で呼ばれる __CFStringMakeConstantString() が初めて呼ばれたときに作られる。ちなみに、容量の初期値は 2500。
CFSTR() の実装
では本題、実装を見ていこう。CFSTR() マクロの定義はこう。
CoreFoundation/String.subproj/CFString.h
CoreFoundation/Strign.subproj/CFString.h
#ifdef __CONSTANT_CFSTRINGS__
#define CFSTR(cStr) ((CFStringRef) __builtin___CFStringMakeConstantString ("" cStr ""))
#else
#define CFSTR(cStr) __CFStringMakeConstantString("" cStr "")
#endif
というわけで、__CFStringMakeConstantString() が呼び出される。これは定数文字列を作るための関数。この中での処理の流れは次のような感じだ。
- キャッシュテーブルがあるかどうかチェック。無いなら作る
- キャッシュテーブルの中に対応する値を取り出す
- 対応する値が無いなら文字列を作って、テーブルに入れる
- 値を返す
これを頭において、実際のコードを抜き出してみたよ。
CoreFoundation/String.subproj/CFString.c
CFStringRef __CFStringMakeConstantString(const char *cStr) {
CFStringRef result;
/* キャッシュテーブルがあるかどうかチェック */
if (constantStringTable == NULL) {
/* キャッシュテーブルが無かった場合 */
CFDictionaryKeyCallBacks constantStringCallBacks =
{0, NULL, NULL, __cStrCopyDescription, __cStrEqual, __cStrHash};
/* キャッシュテーブルをつくる */
constantStringTable = CFDictionaryCreateMutable(
NULL, 0, &constantStringCallBacks, &kCFTypeDictionaryValueCallBacks);
_CFDictionarySetCapacity(constantStringTable, 2500); // avoid lots of rehashing
...
}
__CFSpinLock(&_CFSTRLock);
/* キャッシュテーブルから文字列を取り出す */
if (result = (CFStringRef)CFDictionaryGetValue(constantStringTable, cStr)) {
/* 文字列があった場合 */
__CFSpinUnlock(&_CFSTRLock);
} else {
/* 文字列が無かった場合 */
__CFSpinUnlock(&_CFSTRLock);
/* ASCII かどうかチェック */
...
if (!isASCII) {
/* ASCII じゃなかったときの処理 */
}
/* 文字列をつくる */
result = CFStringCreateWithCString(
constantStringAllocatorForDebugging, cStr, kCFStringEncodingMacRoman);
/* エラーチェックなど */
...
{
...
/* キャッシュテーブルに文字列を入れる */
CFIndex count;
__CFSpinLock(&_CFSTRLock);
count = CFDictionaryGetCount(constantStringTable);
CFDictionaryAddValue(constantStringTable, key, result);
if (CFDictionaryGetCount(constantStringTable) == count) {
// add did nothing, someone already put it there
result = (CFStringRef)CFDictionaryGetValue(
constantStringTable, key);
}
__CFSpinUnlock(&_CFSTRLock);
...
}
}
return result;
}
大きな流れはこんな感じ。キャッシュテーブルの処理がメインだ。ASCII じゃないときのエラー処理もあるけど、ここでは省いた。
実際に文字列を作ることになるのは、CFStringCreateWithCString() 関数だ。これについては、次の項で見るよ。