所有権ポリシー
Core Foundation を使用するアプリケーションは、常にオブジェクトにアクセスし、オブジェクトを作成して廃棄します。メモリをリークしないために、Core Foundation はオブジェクトを取得および作成するためのルールを定義しています。
基礎
Core Foundation アプリケーションでメモリ管理を理解しようとするときは、メモリ管理そのものではなく、オブジェクトの所有権という観点から考えてみてください。オブジェクトには、1 人以上の所有者がある場合があります。保持カウントを使用している所有者の数をオブジェクトは記録します。オブジェクトに所有者がない場合 (その保持カウントがゼロになった場合)、そのオブジェクトは廃棄されます (解放されます)。Core Foundation は、オブジェクトの所有権および廃棄に関する以下の規則を定義しています。
- オブジェクトを作成する場合 (直接作成するか、別のオブジェクトのコピーを作成する:作成規則 を参照の事)、オブジェクトを所有します。
- 他の場所からオブジェクトを取得した場合、そのオブジェクトは所有しません。それが廃棄されないようにするには、あなた自身を所有者として追加しなければなりません (CFRetain を使用します)。
- あなたがオブジェクトの所有者であれば、その使用が終わった時 CFRelease を使用して所有権を放棄しなければなりません。
命名規則
Core Foundation を使用してオブジェクトへの参照を取得するには、さまざまな方法があります。Core Foundation の所有権ポリシーに沿って、関数が返すオブジェクトをあなたが所有しているかどうかを知る必要があるため、メモリ管理に関してどのようなアクションを取るべきかをあなたは知る必要があります。Core Foundation は、関数によって返されたオブジェクトを所有しているかどうかを判断できるように、その関数の命名規則を確立しています。簡単に言えば、関数名に "Create" または "Copy" という単語が含まれている場合は、そのオブジェクトを所有しています。関数名に "Get" という単語が含まれている場合、そのオブジェクトは所有していません。これらの規則については、"作成規則" および "取得規則" で詳しく説明しています。
作成規則
Core Foundation 関数には、返されたオブジェクトをあなたが所有した時を示す名前があります。
- 名前に "Create" が埋め込まれたオブジェクト作成関数。
- 名前に "Copy" が埋め込まれたオブジェクト複製関数。
あなたがオブジェクトを所有している場合、CFRelease を使用して所有権を放棄することは、そのオブジェクトの使用を終了した時放棄する責任があなたにあります。
以下の例を考えてみましょう。最初の例は、CFTimeZone に関連した 2 つの作成関数と、CFBundle に関連にた 1 つの作成関数を示しています。
CFTimeZoneRef CFTimeZoneCreateWithTimeIntervalFromGMT (CFAllocatorRef allocator, CFTimeInterval ti); CFDictionaryRef CFTimeZoneCopyAbbreviationDictionary (void); CFBundleRef CFBundleCreate (CFAllocatorRef allocator, CFURLRef bundleURL);
最初の関数は名前に "Create" という単語を含み、新しい CFTimeZone オブジェクトを作成します。あなたはこのオブジェクトを所有しており、所有権を放棄するのはあなたの責任です。2 番目の関数は、その名前に "Copy" という単語を含み、タイムゾーンオブジェクトの属性のコピーを作成します。(これは属性自体を取得することとは異なります。取得規則 を参照してください)。また、このオブジェクトを所有していれば、所有権を放棄するのはあなたの責任です。3 番目の関数 CFBundleCreate は、その名前に "Create" という単語が含まれていますが、ドキュメントには既存の CFBundle が返されることが記載されています。しかし、新しいオブジェクトが実際に作成されるかどうかにかかわらず、このオブジェクトを所有しています。既存のオブジェクトが返された場合は、保持カウントが増分され、所有権を放棄するのはあなたの責任です。
次の例は、より複雑に見えるかもしれませんが、それでも同じ単純な規則に従います。
/* from CFBag.h */ CF_EXPORT CFBagRef CFBagCreate(CFAllocatorRef allocator, const void **values, CFIndex numValues, const CFBagCallBacks *callBacks); CF_EXPORT CFMutableBagRef CFBagCreateMutableCopy(CFAllocatorRef allocator, CFIndex capacity, CFBagRef bag);
CFBag 関数の CFBagCreateMutableCopy は、その名前に "Create" と "Copy" の両方を持ちます。関数名に "Create" という単語が含まれているため、これは作成関数です。また、最初の引数は CFAllocatorRef 型であることに注意してください。これはさらなるヒントとして役立ちます。この関数内の "Copy"は、関数が CFBagRef 引数をとり、オブジェクトの複製を生成するというヒントです。また、ソースコレクションの要素オブジェクトに何が起こるかを示します。これらは、新しく作成された bag にコピーされます。関数名の 2 次 "Copy" および "NoCopy" の部分文字列は、一部のソースオブジェクトが所有するオブジェクトがどのように扱われるか、つまりコピーされているかどうかを示します。
取得規則
Get 関数などの作成関数やコピー関数以外の Core Foundation 関数からオブジェクトを受け取った場合、そのオブジェクトを所有せず、オブジェクトの寿命を確認することはできません。そのようなオブジェクトが使用中に廃棄されないようにするには、(CFRetain 関数を使用して) 所有権を主張しなければなりません。その使用を終えたときには所有権を放棄する責任があなたにはあります。
CFAttributedStringGetString 関数を考えて見ましょう。この関数は、属性付き文字列の支援字列を返します。
CFStringRef CFAttributedStringGetString (CFAttributedStringRef aStr);
属性付き文字列が 解放された 場合、それは支援文字列の所有権を放棄します。属性付き文字列が支援文字列の唯一の所有者であった場合、支援文字列には所有者がなくなり、それ自体は解放されます。属性付き文字列が破棄された後で支援文字列にアクセスする必要がある場合は、所有権に (CFRetain を使用して) 要求するか、またはそのコピーを作成しなければなりません。それの使用を終了したら所有権を (CFRelease を使用して) 放棄しなければなりません。そうしないと、メモリーリークが発生します。
インスタンス変数と渡すパラメータ
基本ルールの結果として、オブジェクトを別のオブジェクトに渡すとき (関数のパラメータとして) には、渡されたオブジェクトを所有者が維持する必要がある場合、そのオブジェクトの所有権を受信した方が取得する必要があります。
これを理解するには、受信するオブジェクトの実装者の位置に自分自身を置きます。関数がパラメータとしてオブジェクトを受け取ると、受信者は最初にそのオブジェクトを所有しません。したがって、受信者が if (CFRetain を使用して) の所有権を取らない限り、オブジェクトはいつでも割り当て解除される可能性があります。受信者がオブジェクトの使用を終了すると、新しい値に置き換えられるか、または受信者自身が割り当て解除されているため、受信者は所有権を放棄する責任があります (CFRelease を使用して)。
所有権の例
実行時エラーやメモリーリークを防止するには、Core Foundation オブジェクトが受信し、渡され、または返されるたびに、Core Foundation 所有権ポリシーを一貫して適用する必要があります。作成していないオブジェクトの所有者になる必要がある理由を理解するには、この例を考慮してください。別のオブジェクトから値を取得したとします。値が "含む" オブジェクトがその後割り当て解除されると、"含まれる" オブジェクトの所有権を放棄します。含まれるオブジェクトが値の唯一の所有者である場合、値には所有者がなく、割り当て解除されます。解放されたオブジェクトへの参照がまだあるので。使用しようとすると、アプリケーションはクラッシュします。
以下のコードの断片は、3 つの一般的な状況を示しています。Set アクセッサー関数、Get アクセッサー関数、および特定の条件が満たされるまで Core Foundation オブジェクトを保持する関数です。最初に Set 関数です:
static CFStringRef title = NULL; void SetTitle(CFStringRef newTitle) { CFStringRef temp = title; title = CFStringCreateCopy(kCFAllocatorDefault , newTitle); CFRelease(temp); }
上記の例では、静的な CFStringRef 変数を使用して、保持されている CFString オブジェクトを保持しています。それを格納するために他の手段を使うことができますが、受信する関数にローカルではない場所に置く必要があります。この関数は、新しいタイトルをコピーして古いタイトルを解放する前に、ローカル変数に現在のタイトルを代入します。渡された CFString オブジェクトが、現在保持されているものと同じオブジェクトである場合、コピー後に解放されます。
上記の例では、オブジェクトは単純に保持されるのではなくコピーされます。(所有権の観点から見れば、これらは同等です:基礎 を参照してください)。この理由は、title プロパティが属性と見なされるためです。それはアクセッサメソッド以外で変更するべきではないものです。パラメータが CFStringRef と入力されていても、CFMutableString オブジェクトへの参照が渡される可能性があり、これにより、値が外部的に変更される可能性があります。したがって、オブジェクトを保持している間は変更されないようにオブジェクトをコピーします。オブジェクトが変更可能であるか、または独自のバージョンが必要な場合は、オブジェクトをコピーする必要があります。オブジェクトが関係ありとみなされる場合は、そのオブジェクトを保持する必要があります。
対応する Get 関数ははるかに簡単です:
CFStringRef GetTitle() { return title; }
単にオブジェクトを返すだけで弱い参照が返されます。つまり、ポインタ値は受信側の変数にコピーされますが、参照カウントは変更されません。コレクションの要素が返されたときも同じことが起こります。
以下の関数は、不要になるまでコレクションから取得したオブジェクトを保持してから解放します。オブジェクトは不変であるとみなされます。
static CFStringRef title = NULL; void MyFunction(CFDictionary dict, Boolean aFlag) { if (!title && !aFlag) { title = (CFStringRef)CFDictionaryGetValue(dict, CFSTR(“title”)); title = CFRetain(title); } /* Do something with title here. */ if (aFlag) { CFRelease(title); } }
以下の例は、数値オブジェクトを配列に渡す方法を示しています。配列の呼び出し関数は、コレクションに追加されたオブジェクトが保持されている (コレクションがそれらを所有している) ことを指定しているため、配列に追加された後に数値を解放できます。
float myFloat = 10.523987; CFNumberRef myNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &myFloat); CFMutableArrayRef myArray = CFArrayCreateMutable(kCFAllocatorDefault, 2, &kCFTypeArrayCallBacks); CFArrayAppendValue(myArray, myNumber); CFRelease(myNumber); // code continues...
(a) 配列を解放し、(b) 配列を解放した後に引き続き number 変数を使用する場合、潜在的な落とし穴があることに注意してください。
CFRelease(myArray); CFNumberRef otherNumber = // ... ; CFComparisonResult comparison = CFNumberCompare(myNumber, otherNumber, NULL);
数値または配列を保持していないか、またはその所有権を維持する他のオブジェクトに渡されない限り、コードは比較関数で失敗します。他のオブジェクトが配列または数字を所有していない場合、配列が解放されると、配列も割り当て解除され、その内容が解放されます。この状況では、これも数値の割り当て解除につながるため、比較関数は解放されたオブジェクトで動作し、クラッシュします。
前の章 次の章