MacOSプログラミング/Cocoaでのアーカイブとシリアライズ機能 アーカイブ編
... 2009/10/09 (Fri)
アーカイブについて
ADC: Archives
アーカイブとは - アーカイブ︵アーカイバー︶はオブジェクトと値を、オブジェクトの同一性(identity)とオブジェクトや値同士の関係を保持しながら、(CPU)アーキテクチャに依存しないバイトストリームに変換する機能を提供する。 - CocoaのアーカイブはObjective-CとJavaのオブジェクト、スカラー値、配列、構造体、文字列を保存出来る。アーカイブは、アーキテクチャによって定義が異なる型については、サポートしない。 - (アーカイブでサポートされない)アーキテクチャによって定義が異なる型 - union - void* - 関数ポインタ - long chains of pointers - 以下、アーカイブにオブジェクトや値を入れてバイトストリームにすることを﹁エンコード﹂、バイトストリームからオブジェクトや値を取り出すことを﹁デコード﹂と言う。 - アーカイブはオブジェクト型の情報をデータと一緒に保持するので、バイトストリームから展開されたオブジェクトは、エンコードした時の型と、通常は同じになる。ただし、例外もある。”Making Substitutions During Coding”で、これに対する例外のケースについて説明する。
Coderとは - オブジェクトはCoderオブジェクトを通して読まれたり書かれたりする。Coderオブジェクトは、NSCoderを実装する何らかの具象クラスのこと。 - NSCoderインターフェイスは、うけ取ったデータをファイルに書いたり、他のプロセスに送ったりといった機能を実装するために必要なインターフェイスを定義している。 NSCoderは、さらに、それらのプロセスの逆方向の処理を行うためのインターフェイスも定義している。 - NSCoderのサブクラスは、これらのインターフェイスから、自分の役割のものだけを実装している。 - たとえば、NSCoderのサブクラスには3つのグループがある。 - ︵シーケンシャルアーカイバーとして︶NSArchiver, NSUnarchiver - ︵キーアーカイバーとして︶NSKeyedArchiver,NSKeyedUnarchiver - (分散オブジェクトとして) NSPortCoder
ルートオブジェクト - ルートオブジェクトとは、アーカイブされるオブジェクトグラフのスタート地点である。アーカイバーによっては、シリアライズするグラフのルートがどれなのか、ユーザーが決める必要がある。 - encodeRootObjectメソッドを使ってルートオブジェクトを指定する。 - NSCoder自身はencodeRootObjectを実装せず、encodeObjectを呼ぶだけ。
コンディショナルオブジェクト - 参照関係の中には、積極的に保存するオブジェクトグラフの中に含めたいとは思わないけど、そのオブジェクトがもし存在するなら関係性は保持してほしい、と思うような関係がままある。そのような場合の関係は、コンディショナルオブジェクトとしてアーカイブすることで可能になる。 - encodeConditionalObject, encodeConditionalObject:forKey を使うことで指定可能。 - あるオブジェクトが、それに関連するすべてのオブジェクトからコンディショナルオブジェクトとしてエンコードされた場合、そのオブジェクトは保持されず、nilとしてデコードされる。誰かがコンディショナルでないオブジェクトとしてエンコードすると、全員の関係がデコードされる。 - つまり、要するに、ConditionalObjectとは、スマートポインタで言うweakリファレンスの様なものと考えられる。 - NSCoder自身はコンディショナルオブジェクトの機能を実装せず、encodeObjectにデレゲートするだけ。 NSArchiver, NSKeyedArchiverはコンディショナルオブジェクトの完全なサポートを提供する。
キーアーカイブ(Keyed Archives)とは - キーアーカイブは、NSKeyArchiver、NSKeyUnarchiver を使ったアーカイブの事。 - キーアーカイブはシーケンシャルアーカイブとは違い、すべてのエンコードされる要素が名前︵キー︶を持っている。 - アーカイブをデコードする時には、デコード順にかかわらず、キーを指定して任意の順番でデコードすることが出来る。 - この特性は、あなたのクラスがバージョンを重ねた際に、下位互換性や上位互換性を維持することを容易にしてくれる。 - OSX10.2以降では、Keyed Archiveの方が望ましい形式とされている。というか、10.2以降では、シーケンシャルアーカイブはdeprecatedになっている。
値を名前付けする - オブジェクトにエンコードされる値は任意の文字列で名前付けされることが出来る。 - アーカイブは一つ一つのオブジェクトに対して階層化されており、オブジェクトごとに独自のネームスペースを提供する。オブジェクトのインスタンス変数に近い。このため、キーはエンコードされるオブジェクトの内部でのみユニークであればよい。これは、オブジェクトA,Bが内部の値をエンコードする際、同じキーが使われても大丈夫という事である。A,Bが同じクラスのインスタンスであっても問題ない。 - ただし、親クラスで使われているキーをサブクラスで使うと、それは衝突するので避けなければならない。 - フレームワークによって提供されているクラスのサブクラスを作成する場合は、キーが衝突しないことを保証するために、プリフィクスをつけることが望ましい。 - 推奨されるプリフィクスは、サブクラスのフルネームそのもの、もしくはバンドルIDなどである。 - CocoaのクラスはプリフィクスにNSを使っている。 - キーに$を使うのはやめた方がいい。Keyed Archiver, Unarchiverは$をinternal valuesに使用しているためである。NSKeyArchiver, NSKeyUnarchiverにはユーザーが$をつけても大丈夫なように工夫する仕組みが備わっているが、それを経由することでアーカイブの処理速度が低下するので、避ける方が望ましい。
鍵に対する値が無い場合どうなるのか - 鍵に対応する値が存在しない場合、Unarchiverはユーザーが使用したデコードメソッドのデフォルト値を返す。 - 一般的なデータ型なら0、オブジェクトならnil, booleanはNO, real は 0.0 sizeなら NSZeroSize, 等々 - キーに対する値が存在するかどうかをチェックしたい場合は、containsValueForKey を使用する - パフォーマンス的な観点から、一つ一つのキーがあるかどうかをチェックするのは、やめた方がいい。
型の強制 - NSKeyedUnarchiverは、いくつかの型の強制を行う機能を持っている。型の強制とは、エンコードしたときの型ではない型でデコードすることを言う。 - Integer系の型であれば、32bit, 64bit のどの型のIntegerにも出来る。Floatも同様。 - デコードした値が強制使用とした型に対して大きすぎる場合、NSRangeExceptionがスローされる。 - 同じように、強制不可な型でデコードを行おうとすると、NSInvalidUnarchiveOperationExceptionがスローされる。 - たとえば、int型をfloatでデコードしようとすると、NSInvalidUnarchiveOperationExceptionが発生する。
クラスのバージョニング - キーアーカイブは、シーケンシャルアーカイブの様なクラスに対するバージョン対応機能を提供していない。アーカイバによる自動的なバージョンニングは存在しない。 - Unarchiverは自分でクラスバージョンのミスマッチ判定をしないので、デコード結果からユーザーが判断することになる。必要十分な条件がデコード出来れば成功とすればいい。
ルートオブジェクト - キーアーカイブには、ルートオブジェクトとその他のオブジェクトの区別はない。 - シーケンシャルアーカイブ(NSArchiver)との互換性を維持するため、 archiveRootObject:toFile:などを使用すると、アーカイブ内に単一のオブジェクトグラフを作成する。 - このオブジェクトグラフをデコードする際の注意点として、 unarchiveObjectWithFile を使用してデコードしなければならない点がある。注意。
デレゲート - NSKeyedArchiver, NSKeyedUnarchiverには、シーケンシャルアーカイブと違い、デレゲートを持つことが出来る。 - デレゲートはオブジェクトがエンコード・デコードされるたびに呼ばれる。これをつかってエンコード・デコード時にオブジェクトの中身を変更することが可能。
キーを使わないエンコード - キーアーカイブはすべての値に対してキーを用意しなくても良い。この場合、シーケンシャルな処理としてエンコードされる。キーのないエンコード処理は、シーケンシャルアーカイブと同じ制約が課される。つまり、以下の制約がある。 - (1) 順番は大事。エンコードした順にデコードする必要が有る。 - (2) エンコード、デコード正しいメソッド︵の対︶を使用する必要がある。 - キーを使用しない場合でも、NSUnarchiverでデコードできる、というわけではない。 - 結論として、キーを使わないエンコードは、使用可能とはいえお勧めできないので、使わない方がよい。
シーケンシャルアーカイブ(Sequential Archives)とは - シーケンシャルアーカイブは、10.0~10.1では唯一のアーカイバーだったが、10.2以降ではdeprecatedとされた。10.2以降の現在、このアーカイバーは使うべきでない。 - シーケンシャルアーカイブはNSArchiverとNSUnarchiverで作成される。 - シーケンシャルアーカイブは、ファイルやNSDataに対してアーカイブすることが可能。 - デレゲートは設定不能 - シーケンシャルアーカイブは、エンコードとデコードが同じ順番で処理されなければならない。 - 値のスキップやランダムなリクエストは不可能。 - データタイプは完全に一致しないとならず、型の強制は不可能。 - アーカイブには1つのルートオブジェクトのみ存在可能。 - NSUnarchiverはデコード時にいろいろなチェックを行い、デコード可能かどうかを確認する。 - クラス名が一緒か? - クラス名は既知か? - 要求されている型名は次のデータと一緒か? - 不明なコードがないか? - 文字列が行き過ぎたり、欠けていたりしないか? - 下位の互換性をサポートするために、シーケンシャルアーカイブにはsetVersionという、クラスバージョンを設定する機能がある。 - バージョン情報は、デコード時に利用し、それでif-elseで対応することが可能。 - ただし、この方法で下位互換性、上位互換性をを維持するのは非常に大変。なので、キーアーカイブを使用するべき。
まとめ - アーカイブを利用することで、アーキテクチャに依存しない形でデータを保存・復元出来る。 - アーカイブを行う際には、キーアーカイブを使う。 - 値をエンコード・デコードする際には、必ずキーを使用する。 - キーアーカイブには複数のオブジェクトグラフを保持できるので、ルートオブジェクトについては気にしない。 - あっても無くてもいい参照は、ConditionalObjectとしてエンコードする
ADC: Archives
アーカイブとは - アーカイブ︵アーカイバー︶はオブジェクトと値を、オブジェクトの同一性(identity)とオブジェクトや値同士の関係を保持しながら、(CPU)アーキテクチャに依存しないバイトストリームに変換する機能を提供する。 - CocoaのアーカイブはObjective-CとJavaのオブジェクト、スカラー値、配列、構造体、文字列を保存出来る。アーカイブは、アーキテクチャによって定義が異なる型については、サポートしない。 - (アーカイブでサポートされない)アーキテクチャによって定義が異なる型 - union - void* - 関数ポインタ - long chains of pointers - 以下、アーカイブにオブジェクトや値を入れてバイトストリームにすることを﹁エンコード﹂、バイトストリームからオブジェクトや値を取り出すことを﹁デコード﹂と言う。 - アーカイブはオブジェクト型の情報をデータと一緒に保持するので、バイトストリームから展開されたオブジェクトは、エンコードした時の型と、通常は同じになる。ただし、例外もある。”Making Substitutions During Coding”で、これに対する例外のケースについて説明する。
Coderとは - オブジェクトはCoderオブジェクトを通して読まれたり書かれたりする。Coderオブジェクトは、NSCoderを実装する何らかの具象クラスのこと。 - NSCoderインターフェイスは、うけ取ったデータをファイルに書いたり、他のプロセスに送ったりといった機能を実装するために必要なインターフェイスを定義している。 NSCoderは、さらに、それらのプロセスの逆方向の処理を行うためのインターフェイスも定義している。 - NSCoderのサブクラスは、これらのインターフェイスから、自分の役割のものだけを実装している。 - たとえば、NSCoderのサブクラスには3つのグループがある。 - ︵シーケンシャルアーカイバーとして︶NSArchiver, NSUnarchiver - ︵キーアーカイバーとして︶NSKeyedArchiver,NSKeyedUnarchiver - (分散オブジェクトとして) NSPortCoder
ルートオブジェクト - ルートオブジェクトとは、アーカイブされるオブジェクトグラフのスタート地点である。アーカイバーによっては、シリアライズするグラフのルートがどれなのか、ユーザーが決める必要がある。 - encodeRootObjectメソッドを使ってルートオブジェクトを指定する。 - NSCoder自身はencodeRootObjectを実装せず、encodeObjectを呼ぶだけ。
コンディショナルオブジェクト - 参照関係の中には、積極的に保存するオブジェクトグラフの中に含めたいとは思わないけど、そのオブジェクトがもし存在するなら関係性は保持してほしい、と思うような関係がままある。そのような場合の関係は、コンディショナルオブジェクトとしてアーカイブすることで可能になる。 - encodeConditionalObject, encodeConditionalObject:forKey を使うことで指定可能。 - あるオブジェクトが、それに関連するすべてのオブジェクトからコンディショナルオブジェクトとしてエンコードされた場合、そのオブジェクトは保持されず、nilとしてデコードされる。誰かがコンディショナルでないオブジェクトとしてエンコードすると、全員の関係がデコードされる。 - つまり、要するに、ConditionalObjectとは、スマートポインタで言うweakリファレンスの様なものと考えられる。 - NSCoder自身はコンディショナルオブジェクトの機能を実装せず、encodeObjectにデレゲートするだけ。 NSArchiver, NSKeyedArchiverはコンディショナルオブジェクトの完全なサポートを提供する。
キーアーカイブ(Keyed Archives)とは - キーアーカイブは、NSKeyArchiver、NSKeyUnarchiver を使ったアーカイブの事。 - キーアーカイブはシーケンシャルアーカイブとは違い、すべてのエンコードされる要素が名前︵キー︶を持っている。 - アーカイブをデコードする時には、デコード順にかかわらず、キーを指定して任意の順番でデコードすることが出来る。 - この特性は、あなたのクラスがバージョンを重ねた際に、下位互換性や上位互換性を維持することを容易にしてくれる。 - OSX10.2以降では、Keyed Archiveの方が望ましい形式とされている。というか、10.2以降では、シーケンシャルアーカイブはdeprecatedになっている。
値を名前付けする - オブジェクトにエンコードされる値は任意の文字列で名前付けされることが出来る。 - アーカイブは一つ一つのオブジェクトに対して階層化されており、オブジェクトごとに独自のネームスペースを提供する。オブジェクトのインスタンス変数に近い。このため、キーはエンコードされるオブジェクトの内部でのみユニークであればよい。これは、オブジェクトA,Bが内部の値をエンコードする際、同じキーが使われても大丈夫という事である。A,Bが同じクラスのインスタンスであっても問題ない。 - ただし、親クラスで使われているキーをサブクラスで使うと、それは衝突するので避けなければならない。 - フレームワークによって提供されているクラスのサブクラスを作成する場合は、キーが衝突しないことを保証するために、プリフィクスをつけることが望ましい。 - 推奨されるプリフィクスは、サブクラスのフルネームそのもの、もしくはバンドルIDなどである。 - CocoaのクラスはプリフィクスにNSを使っている。 - キーに$を使うのはやめた方がいい。Keyed Archiver, Unarchiverは$をinternal valuesに使用しているためである。NSKeyArchiver, NSKeyUnarchiverにはユーザーが$をつけても大丈夫なように工夫する仕組みが備わっているが、それを経由することでアーカイブの処理速度が低下するので、避ける方が望ましい。
鍵に対する値が無い場合どうなるのか - 鍵に対応する値が存在しない場合、Unarchiverはユーザーが使用したデコードメソッドのデフォルト値を返す。 - 一般的なデータ型なら0、オブジェクトならnil, booleanはNO, real は 0.0 sizeなら NSZeroSize, 等々 - キーに対する値が存在するかどうかをチェックしたい場合は、containsValueForKey を使用する - パフォーマンス的な観点から、一つ一つのキーがあるかどうかをチェックするのは、やめた方がいい。
型の強制 - NSKeyedUnarchiverは、いくつかの型の強制を行う機能を持っている。型の強制とは、エンコードしたときの型ではない型でデコードすることを言う。 - Integer系の型であれば、32bit, 64bit のどの型のIntegerにも出来る。Floatも同様。 - デコードした値が強制使用とした型に対して大きすぎる場合、NSRangeExceptionがスローされる。 - 同じように、強制不可な型でデコードを行おうとすると、NSInvalidUnarchiveOperationExceptionがスローされる。 - たとえば、int型をfloatでデコードしようとすると、NSInvalidUnarchiveOperationExceptionが発生する。
クラスのバージョニング - キーアーカイブは、シーケンシャルアーカイブの様なクラスに対するバージョン対応機能を提供していない。アーカイバによる自動的なバージョンニングは存在しない。 - Unarchiverは自分でクラスバージョンのミスマッチ判定をしないので、デコード結果からユーザーが判断することになる。必要十分な条件がデコード出来れば成功とすればいい。
ルートオブジェクト - キーアーカイブには、ルートオブジェクトとその他のオブジェクトの区別はない。 - シーケンシャルアーカイブ(NSArchiver)との互換性を維持するため、 archiveRootObject:toFile:などを使用すると、アーカイブ内に単一のオブジェクトグラフを作成する。 - このオブジェクトグラフをデコードする際の注意点として、 unarchiveObjectWithFile を使用してデコードしなければならない点がある。注意。
デレゲート - NSKeyedArchiver, NSKeyedUnarchiverには、シーケンシャルアーカイブと違い、デレゲートを持つことが出来る。 - デレゲートはオブジェクトがエンコード・デコードされるたびに呼ばれる。これをつかってエンコード・デコード時にオブジェクトの中身を変更することが可能。
キーを使わないエンコード - キーアーカイブはすべての値に対してキーを用意しなくても良い。この場合、シーケンシャルな処理としてエンコードされる。キーのないエンコード処理は、シーケンシャルアーカイブと同じ制約が課される。つまり、以下の制約がある。 - (1) 順番は大事。エンコードした順にデコードする必要が有る。 - (2) エンコード、デコード正しいメソッド︵の対︶を使用する必要がある。 - キーを使用しない場合でも、NSUnarchiverでデコードできる、というわけではない。 - 結論として、キーを使わないエンコードは、使用可能とはいえお勧めできないので、使わない方がよい。
シーケンシャルアーカイブ(Sequential Archives)とは - シーケンシャルアーカイブは、10.0~10.1では唯一のアーカイバーだったが、10.2以降ではdeprecatedとされた。10.2以降の現在、このアーカイバーは使うべきでない。 - シーケンシャルアーカイブはNSArchiverとNSUnarchiverで作成される。 - シーケンシャルアーカイブは、ファイルやNSDataに対してアーカイブすることが可能。 - デレゲートは設定不能 - シーケンシャルアーカイブは、エンコードとデコードが同じ順番で処理されなければならない。 - 値のスキップやランダムなリクエストは不可能。 - データタイプは完全に一致しないとならず、型の強制は不可能。 - アーカイブには1つのルートオブジェクトのみ存在可能。 - NSUnarchiverはデコード時にいろいろなチェックを行い、デコード可能かどうかを確認する。 - クラス名が一緒か? - クラス名は既知か? - 要求されている型名は次のデータと一緒か? - 不明なコードがないか? - 文字列が行き過ぎたり、欠けていたりしないか? - 下位の互換性をサポートするために、シーケンシャルアーカイブにはsetVersionという、クラスバージョンを設定する機能がある。 - バージョン情報は、デコード時に利用し、それでif-elseで対応することが可能。 - ただし、この方法で下位互換性、上位互換性をを維持するのは非常に大変。なので、キーアーカイブを使用するべき。
まとめ - アーカイブを利用することで、アーキテクチャに依存しない形でデータを保存・復元出来る。 - アーカイブを行う際には、キーアーカイブを使う。 - 値をエンコード・デコードする際には、必ずキーを使用する。 - キーアーカイブには複数のオブジェクトグラフを保持できるので、ルートオブジェクトについては気にしない。 - あっても無くてもいい参照は、ConditionalObjectとしてエンコードする
Comment
Trackback
Trackback URL:
http://deathcube.blog36.fc2.com/tb.php/17-2916dc4f