MacOS/Dashboardがハングした際に復旧するには

MacOS X 10.6(Snow Leopard) にして以降、Exposeを通したDashboardの表示がたびたびできなくなることがあります。理由はよく分からないのですが、気づくとF12キーを押してもDashboardが起動しなくなり、代わりにビープ音が再生されて、何かのエラーが発生したことを伝えられます。
一時期はそのたびにログアウト&ログインなどをしていたのですが、それも面倒なので、素早く復旧する方法を調べました。

1. ターミナルを開く
2. "killall Dock" と入力
以上。

これで、またF12を押すと、Dashboardが表示されるようになります。

仕組みとしては、Dock.app内のDashboardClientというアプリケーションがDashboardの制御を司っているようなのですが、killallコマンドによってDockと名のつくプロセスがすべて殺されるためリセットされる、とか、そんな感じのようです。

同じ現象に困っている人は、上記の手法を試してみるといいかもしれません。
ただし、プロセスの強制終了は無理矢理な手法ではあるので、あくまで自己責任でお願いします。
(と、わざわざ書くのはこのブログの主旨に反するのですが、念のため)

MacOSプログラミング/Guard Malloc(libgmalloc) について

Guard Mallocはmalloc, callocなどで確保したメモリに対して不正な操作を行ってしまう類のバグの検出を助けるデバッグ用のライブラリです。Guard Mallocを使ってアプリケーションを実行すると、そうしたメモリに対してのバグがある場合、アプリケーションがバグの位置でハングアップします。

- Manual page for libgmalloc

Xcode上でのGuard Mallocの使い方
メニューから「実行>Guard Mallocを有効にする」を選択して、チェックをつけるとGuard Mallocを有効に出来ます。Guard Mallocには各種オプションがありますが、これは実行時の環境変数をセットすることで設定します。

Guard Mallocの原理
Guard Mallocは、malloc, free, NSZoneMallocとその派生系(callocなど)をリプレースし、バグの検出に特化したやりかたでメモリを確保します。

ここから先の原理の話は、仮想メモリに対する理解がないと分からない説明ですが、仮想メモリの説明まで書くと長くなってしまうので、それは割愛します。仮想メモリやページングの動作が分からない方は、適当にgoogle等で調べてください。
なお、仮想メモリに関する勉強をしたいと思う方に対する個人的なお勧めは、手っ取り早い方法ではないですが、
- コンピュータの構成と設計~ハードウエアとソフトウエアのインタフェース 第3版 (下)
の 7.4章 「仮想記憶」 を読むことです。

Guard Malloc版のmallocは、すべてのアロケーションに対して異なるページを割り当てます。そして、freeでは、この割り当てたページを解放するので、プログラムはそのアドレスに触ろうとした瞬間にパスエラー(アドレス例外)を引き起こします。

その他のGuard Mallocの重要な特性は、以下の通りです。
- Guard Mallocのmalloc/freeは、スレッドセーフな操作です。
- (デフォルト設定では)OSX 10.5以降では、確保されるバッファのアドレスは、16byte境界にアラインされます。このため、SSEやAltivec命令で使用するメモリに対して割り当てても問題なく利用できます。
- (デフォルト設定では)OSX 10.5以降では、16バイト単位でのページングになるので、それ以下のサイズでのオーバーランは検出不可能です。たとえば、メモリを10byte確保したブロックの11byte目に意図せず書き込んでしまっても、それは検出されません。これは、SSE/Altivec/Carbon/Cocoaでメモリを利用しやすくするための調整として意図されています。
- アライメント設定はオプションで変更可能です。オプションの詳細については後述します。
- Mac OS X 10.6からは、libgmallocでもmalloc_historyなどのコマンドが利用可能になりました。

環境変数を使ってGuard Mallocを有効にするには
- "DYLD_INSERT_LIBRARIES" 環境変数にlibgmallocのパスを設定します。
 例(gdb上): set env DYLD_INSERT_LIBRARIES to /usr/lib/libgmalloc.dylib
- DYLD_FORCE_FLAT_NAMESPACEの設定は、現在のOSXではもう不要だそうです。

設定可能な環境変数
MALLOC_PROTECT_BEFORE
- このフラグがセットされていると、Guard Mallocはユーザーメモリの先頭にも未使用ページを差し込んで、メモリアンダーランも検出可能にします。

MALLOC_FILL_SPACE
- このフラグがセットされていると、確保したバッファを0x55で埋めます。未初期化バッファのバグ検出に便利。

MALLOC_ALLOW_READS
- ガード用のページに対して、メモリの読み出し行為の許可フラグを設定する。保護領域の読み出し操作によるハングはしなくなる

MALLOC_VECTOR_SIZE
- バッファのデフォルトアライメントサイズ(byte) を設定します。

MALLOC_WORD_SIZE
- バッファはかならずワード値以上にアライメントますが、そのワードのサイズを指定します。
 (ちゃんと確認してないが、デフォルトはおそらく4もしくは8(32bit/64bit) でしょう)

MALLOC_STRICT_SIZE
- バッファの終端がページの終端になるように厳密に処理するかどうかを指定します。このオプションを使うと、オーバーランに対するより厳密な検出が出来るようになりますが、Carbon/Cocoa/SSE/Altivec等を使ったコードはうまく動作しなくなる可能性があります。

MALLOC_PERMIT_INSANE_REQUESTS
- 明らかに不正と思われるサイズのリクエスト(100MB以上)に対して、デバッガでトラップするようにします。デフォルトは有効で、このフラグを立てるとチェックが行われなくなります。

MALLOC_CHECK_HEADER
- Guard Mallocが確保したメモリブロックのマジックナンバーをチェックし、破壊確認を行います。

MALLOC_NO_BACKTRACE
- Guard Mallocはメモリ確保時にスタックトレースを保存しますが、この機能をOFFにします。

MallocStackLogging
- このフラグがONになっていると、標準のmallocスタックロギングが有効になります。これにより、malloc_historyコマンドが利用可能になり、それを経由したmalloc/freeイベントの確認が可能になります。MallocStackLoggingの利用は、Guard Mallocが内部で利用するスタックバックトレースの保存とは独立に動作しており、連動はしていないので、MALLOC_NO_BACKTRACEとの依存関係はありません。

Guard Mallocで作成されるチャンクの構造
Guard Mallocで確保されるメモリブロックは、以下のような構造をしています。

メモリブロックの先頭から:
- チャンクサイズ(byte): (0x60(ヘッダサイズ) + 確保サイズ をページ境界に拡張した値) が格納される
- スレッドID
- トップ20フレーム分のメモリ確保時のスタックバックトレース
--- 20フレームより深いところで呼ばれた場合、切り取られて直近の20フレームのみが入る
--- 20フレームより浅いところで呼ばれた場合、未使用部分には0が詰められる
- マジックナンバー(0xdeadbeef)
- ユーザーバッファの先頭 (ここからユーザーに渡されるメモリ)

Guard Mallocの制限
Guard Mallocは、残念ながらなんの制限も課さずに使えるシステム、という訳ではありません。
制限事項も存在します。Guard Mallocの使用には、以下の制限があります。

- メモリブロックが最大500,000個程度しか確保出来ない(ページ数の上限に達するため)
- 非効率なページの使い方をするので、仮想メモリのスワッピング回数が増加し、パフォーマンスに悪影響が出やすい

まとめ
Guard Mallocは面倒なバグの検出を助けてくれる強力なツールです。制限を理解し、オプションを上手く使って楽をしましょう。

MacOSプログラミング/Xcode Debugging Tips

Xcode環境でデバッグを行う際に役に立ちそうな情報をまとめました。
Xcodeはgdbのフロントエンドとして動作するビジュアルデバッガを提供していますが、VisualStudioなどを使い慣れていると、ぱっと見足りない機能があるように見えるというか、「あれ、コレってどうやるの?」みたいな事が、いくつかあります。

このページでは、そんな経験を何度かした私が関連ドキュメントの一部を調べて、これはと思った機能を紹介します。そんなわけで、Xcodeのデバッガの使い方がそもそも分からないというような初心者には適さない内容ではありますが、何となく使っているだけでは分からない、あるいは見落としやすい内容をメインに書いています。

なお、Guard Malloc(libgmalloc)についてはそれ自体の情報量が多いので、別エントリにまとめます。

参考情報:
ここに載っている情報は、以下の情報を参考に意訳したものです。正確な内容を目指してはいますが恐らく間違いもあります。
おかしいな、と思ったら一次情報を参照してください。
- Xcode 3 Unleashed Chapter 22. "More About Debugging"
- Xcode Debugging Guide

ブレークポイント
- メニュー「実行>表示>ブレークポイント」 で、ブレークポイントのリストが表示されます。ここから、ブレーク条件や、ブレークポイントアクションなどを入力することが出来ます。
- ブレークポイントリストの「条件」の部分に式を書くと、式がtrueの時以外はブレークしないように出来ます。
- シンボル名を直接入力してブレークすることも可能です。フレームワークのグローバル関数などで直接ブレークしたい場合は、シンボルに対して張ると便利なことがあります。"objc_exception_throw" とか、"malloc"とかが該当するかと思います。

ブレークポイントアクション
- ブレークしたい理由が値のチェックである場合、停止させる代わりに値が見たり、なんらかのチェックをするために、ブレークポイントアクションを使うと便利な場合があります。ブレークポイントアクションは、ブレークポイントに到達した時に、ログ出力やgdbのコマンド、AppleScriptのコマンドなどを実行させられます。

- ブレークポイントリストの右端にある「続行」っぽいアイコンをチェックすると、アクションだけ実行して実行を止めずに続行させることが出来ます。

また、Xcode 3.2では「内蔵ブレークポイント」という機能があり、ブレークポイントアクションのプリセットを選択行に対して設定することが出来ます。ブレークポイントアクションがイマイチピンと来ない人は、内蔵ブレークポイントを使ってみるといいのではないでしょうか。

embeded_bp.jpg

ブレークを仕掛けているのに、止まらない場合
- デバッガの実行時、ブレークポイントの表示が(-)となってるものは、ユーザー的には有効にしたいのだが、デバッガとしてはシンボル位置が確定されていないので設定できていない、というモノを指します。このような状況が発生することは、gdbがシンボルの遅延ロード機能を持っていることが、一つの原因です。
- 特定のフレームワークについてこれが発生していて、それを何とかしたい場合、「実行>表示>共有ライブラリ」共有ライブラリのリストを開き、開始レベルを「すべて(All)」に設定することで回避できます。
- また、すべてのシンボルを事前に読みたい(遅延ロードを使用したくない)という場合は、「プレファレンス>デバッグ」で「CFMを使わないでシンボルを読み込む」のチェックを外すことで、そのようにすることが可能です。ただし、これを行うとライブラリ数によってはデバッグまでに起動に時間がかかるようになります。




変数の監視
- 変数が変化したときにブレークするブレークポイントをセットするためには、変数ウインドウで変数を選択して、右クリックで「変数を監視」を選択します。すると、変数の横にルーペのアイコンが設定され、変数の変更によってブレークされるようになります。
- Intel CPUでは、4つのウォッチポイント用レジスタがあるため、変数のウォッチは4つまでは速度に影響を与えずに設定することが出来ます。しかし、4つを超えると、目に見えて分かる速度で実行速度が低下するため、注意が必要です。

デバッグ版フレームワークの利用
Appleの提供するフレームワークにはほぼ必ずデバッグ版、プロファイル版があります。しかし、デバッグ版の構成でも、通常自動的にApple側のフレームワークはデバッグ版が使われることはないようです。フレームワークをデバッグ版にすることで、フレームワーク側から取得できる情報量が増えるようです。
- デバッグ版のフレームワークは、大体名前に_debugのサフィックスがついています。gcc(というか、おそらくリンクローダ)ではリンク時にDYLD_IMAGE_SUFFIX 環境変数をフレームワークにつけるサフィックスとして加味して処理するようです。
- Xcodeでこの設定を行うには、実行可能ファイルの一般設定で、「フレームワークを読み込むときに使用するサフィックス」の設定を変更します。ここに、「なし」「デバッグ」「プロファイル」などがありますので、必要なバリエーションを読むことが出来ます。

アプリケーションを自分のタイミングで起動する
- Xcodeではデフォルトではデバッガを起動するとアプリケーションも同時に処理を開始しますが、一部のケースではこれはやりづらい場合があります。デバッガを起動した際にアプリケーションを自動で実行したくない場合、プロジェクトから実行ファイルを選択して「デバッガの起動後に実行可能ファイルを起動する」のチェックを外すと、「デバッグ」を実行した際、デバッガのみが起動するようになります。
その後、デバッガの「再起動」ボタンを押すと、アプリケーションが起動します。
このように処理を分けることで、たとえばアプリケーションが起動する前にgdbを使ってアレコレ設定を行ったり、ということが出来るようになります。


デバッガの表示と実行行が合わない場合
- XcodeはMac, Windows, Unixのどの行末コードでも適切に対応しますが、ファイル内の行末コードが統一されていない場合、Xcodeのデバッガの行ハイライトが実際の行とずれることがあります。
この問題が発生する場合、「表示>テキスト>行末コード」を使用して、現在のテキストの行末コードを確認しながら、テキストを修正してみてください。

スタックトレースと実行状態が合わない場合
- gccでは末尾再起などスタックトレースに関わる最適化が可能なので、最適化をしているとスタックトレースと実情が合わなくなる場合があります。
- スタックトレースの表示がおかしいと思ったり、コードの実行順が理解できないと思ったら、最適化オプションを確認して、「なし(-O0)」に設定してください。コンパイラが最適化した後のコードをソースレベルデバッグするのは、並大抵のことではないし、不要に困惑させられることの方が多いです。

オブジェクトの内容を詳細表示する
- Xcodeの変数ウインドウは変数を綺麗に表示してくれますが、表示内容が不足な場合があります。たとえば、NSArrayなどでは、オブジェクトが何個あるか、と言ったことはわかるが、実際に何を保持しているのか、ということは、変数ビューでは分からない、分かりづらい場合があります。
このような場合、変数を右クリックして「説明をコンソールに出力する」を実行すると、Cocoaオブジェクトならば、そのオブジェクトのdebugDescriptionメソッドを呼んでオブジェクトの説明を出力できます。
また、それ以外のオブジェクトは、CFShow()関数にその値を渡した結果が出力されます。

gdbのコマンドを使ってデバッグする
gdbでは数多くの便利な機能が提供されていますが、全部を網羅するのは大変です。
ここでは、簡単に理解できて、すぐに役に立つコマンドを少しだけ紹介します。

- オブジェクト内容の詳細表示
 gdbでCocoaオブジェクトを表示するには、poコマンドを使います。
 例: po item
 (itemという変数があると仮定)

- 特定コード・メソッドの実行
 gdbでは、callコマンドで特定のコードを実行することが出来ます。
 例: call (void) CFShow(item)
    call (void) printf("hello world?n")

 ちなみに、(void) は、gdb側が関数の返値を何型で受けるか、の指定です。ここに(int) や(MyObject*)などを指定することも出来ます。有効な型を設定すると、返値は$1, $2 などの変数に格納されます。
 そのようにして格納された変数は、"call (void) CFShow($1)" や "call (void) [$1 myMethod]"のように利用することが出来ます。

- 特定アドレスをフォーマットつきで出力する
 gdbでは、xコマンドで任意の値をフォーマットして表示出来ます。
 例: x /x 0x04322d1

- 式を実行する
 gdbでは、print(p) コマンドで任意の式を出力出来ます。
 例: print sizeof(MyObject)


データフォーマッタを使う
- Xcodeの変数ウインドウにはサマリーというカラムがあり、オブジェクトの内容を分かりやすく表示してくれます。このサマリーは自由にカスタマイズが可能になっています。
編集するには、サマリーをダブルクリックするか、右クリックで「サマリーのフォーマットを編集」を選択します。
- フォーマッタの形は、たとえば以下のような感じになっています。
 例:(x = {[$VAR x]} y = {[$VAR y]})
 出力例: (x = 12.3 y = 4.56)
- $VAR は、そのカラムが対象としているオブジェクトそのものです。
- {} でくくられた部分はコードです。この部分は実行されます。アプリケーションコードに含まれるプログラムを、ユーザーの裁量で設定、実行することが出来ます。
- 構造体(クラス含む)のメンバへのアクセスをする場合は、"%path.To.Value%"のように、%%でくくります。
- スーパークラスのメンバへのアクセスは"%SomeSuperclassName.x%"のようにスーパークラス名を含みます。
- サマリーがカラムの他の値の参照をするには:hogeと後ろにつけます。
 には以下の4つが指定可能です。
 - n: 変数名
 - v: 値
 - t: 型
 - s: サマリー
 例:{[$VAR x]}:s

- データフォーマッタはデータタイプに対して設定されるもので、インスタンスに対して設定されるわけではないので、その点は注意が必要です。つまり、int型のhogeに対して設定を行うと、他のすべてのint型に同じフォーマッタが使用されます。
- 自分で設定したフォーマッタの情報は、以下のファイルに保存されます。
  ~/Library/Application Support/Developer/Xcode/CustomDataViews/CustomDataViews.plist

- 繰り返しとなりますが、フォーマッタにはアプリケーションコードのメッセージ送信を利用することが出来ます。これは、ユーザーにより大きな自由度を与えますが、メッセージ送信によってオブジェクトのステートを変更するようなメソッドをうっかり呼んでしまうと、大きなトラブルの種となります。・・・理由は書かなくても分かりますよね?
- フォーマッタからアプリケーションコードを実行する場合、デバッガは未初期化変数に対してもメソッドを実行するので、この点に気をつける必要があります。妙な返値が返ると、データフォーマッタが例外を吐いて、データが表示出来なくなることがあるようです。


解放済みオブジェクトへの参照を発見する
- Cocoa/Objective-Cでプログラミングをしていると、すでに解放済みのオブジェクトに触ってしまう、ということがあります。それでプログラムがハングしてくれれば、バグであることが分かるのですが、オブジェクトへ送るメッセージによっては、ハングしてくれないこともあります。このようなケースでは、バグを発見するのが困難になるため、よりややこしいバグの温床になる可能性があります。

このような問題を回避するために、Cocoaでは「ゾンビオブジェクト(NSZombie)」を提供しています。ゾンビオブジェクトの機能を有効にすると、一度作成されたオブジェクトはリリース後にはNSZombieオブジェクトにリプレースされます。NSZombieオブジェクトは、どんなメッセージを受け取ってもNSExceptionをスローし、コンソールには「自分は生前なんだったのか」を出力します。
つまり、ゾンビ機能をONにすることで、おかしなオブジェクトがアプリケーションに混ざることを発見しやすくなるわけです。

NSZombieの機能を有効にするには、実行可能ファイルの環境変数に
- NSZombieEnabled=YES
と設定します。

あるいは、Foundation/NSDebug.h に、NSZombieEnabled グローバル変数が定義されているので、これをmain.m のコード内でYESに設定します。

なお、OSX 10.4 以前では、CoreFoundationの互換型(CFTypesという) (NSString等)をゾンビ化するのには、色々とトリックが必要だったそうです。10.5以降では、CFTypesもNSZombie にすることが出来るようになったということです。


解放済みメモリへの参照を発見する
- malloc等で確保したメモリのを解放後に参照してしまう、などのバグを発見したい場合、Guard Malloc を利用します。Guard Mallocは、それ自体への説明がそこそこ量が必要なので、別エントリとして再度解説します。

その他、細かいTIPS
- デバッグ情報のフォーマットは、XcodeではSTABSとDWARFが利用可能ですが、STABSはいわば古いフォーマットで、DWARFの方がトレース出来る情報量が多いので、必ずDWARFを使うべきです。一部の古いMac OS向けのプロジェクトでは、DWARFが利用できないことがあるようです。
- Key-Value Observationを介して管理している無名オブジェクトの中身を知りたい場合は、"po[myObject observationInfo]" で詳細表示が出来るそうです。


さらに踏み込んだ内容を知りたい人に:
ここで紹介した情報は、冒頭の2つのソースの内容を意訳したものですので、そちらの1次情報を是非参照してください。
また、(Xcode 3 Unleashedからの転載ですが) Appleはこれに近しい内容に関するTechNoteをいくつか公開しています。こちらも参考情報としてリストしておきます。

- Mac OS X Debugging Magic
- Getting Started with GDB
- GDB Internals

MacOSプログラミング/Cocoaでのアーカイブとシリアライズ機能 アーカイブ編

アーカイブについて

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としてエンコードする


MacOSプログラミング/Cocoaでのアーカイブとシリアライズ機能 概要編

今作っているアプリで、ファイルフォーマットをアレコレ考えるのが面倒なのでCocoaの機能でやろうと思いArchives/Serializationを調べています。
最近は、この手のドキュメントを読む際には必ずOmni Outlinerで自分向けに簡易マトメを作っているのですが、それを実験的に公開してみます。

元ネタ:
Archives and Serializations Programming Guide for Cocoa

まずは、Object Graphについての説明。このページがちょうど、各種技術の概要になっている。

オブジェクトグラフって何?
- オブジェクト指向のアプリケーションは、オブジェクト同士の複雑な関係性を内包している。
イメージ的にはこんな感じ。

(画像はADCから直に引用してます)

- オブジェクトグラフとは、このオブジェクト同士の相互関連性のことである
- 少ないオブジェクトによるグラフであっても、循環参照や、単一のオブジェクトに対して複数のオブジェクトが参照したりする関係は発生する場合がある。要するに、アプリケーションとして使いやすい形にすると、しばしばファイルやらデータストリームに落とし込むのにはやっかいな形になる。
- このオブジェクトグラフ(通常はその一部だけ)を、ファイルや何か別の形式に変換して、保存したり、別のプロセスや別のPCに送信したいと思うときがある。この処理を手伝ってくれるのが、ArchiveやSerializeといった機能である。
- OSXがオブジェクトグラフをファイルに保存するために、元々用意している仕組みがある。NibファイルとProperty Listの2つである。
- Nibは(UIに関連する)オブジェクトの複雑な関係性を保存する。
- PropertyListはもう少し単純なオブジェクトの関係性と、値を保存する。

アーカイブ(Archives)って何?
- OSXのアーカイブは複雑なオブジェクトグラフを保持する。アーカイブはオブジェクト一つ一つをちゃんと別個のものとして扱い、オブジェクト同士の関連性を保存する。
- 展開されたオブジェクト群は、いくつかの例外はあるが、大体アーカイブされた時の元のオブジェクトグラフの完全なコピーとなる。
- Interface Builderはnibファイルというアーカイブを使用してオブジェクトやその関係性を保存する。
- あなたのアプリケーションは、アーカイブ機能を使うことによって、独自のファイルフォーマットを作成することなしに、独自の情報をオブジェクトグラフの形でファイルの保存することが出来る
- アーカイブをサポートするためには、オブジェクトは必ずNSCodingプロトコルを実装しなければならない。
- ほとんどのFoundationの値系のオブジェクトと、AppkitのオブジェクトはNSCodingを実装しているので、アーカイバーにそのまま放り込むことが出来る

シリアライゼーション(Serialization)って何?
- OSXのシリアライゼーションとは、単純なヒエラルキーの値オブジェクト、たとえば辞書、配列、文字列、バイナリデータを保持するための仕組みである。
- シリアライゼーションは、オブジェクトの値と、ヒエラルキー上におけるその位置(順番)のみを保持する。
- 一つのオブジェクトに対する複数の参照は、シリアライズされたときには複数の別のオブジェクトとして保存される場合がある。つまり、デシリアライズされると、同じ値をもつ別のオブジェクトになる可能性がある。
- Property Listはシリアライゼーションの一例で、アプリケーション属性やユーザープレファレンスを保存する目的で使用されている。
- OSXのシリアライゼーションでは、任意のオブジェクトをシリアライズすることは出来ない。NSArray, NSDictionary, NSString, NSDate,NSNumber,NSDataのみがシリアライズ可能である。NSArrayやNSDictionaryのコンテンツは、同じくこれらのクラスのインスタンスでなければならない。

NOTE: 
OSX10.1以前では、Cocoaはこれらのクラス以外へのシリアライズの機能を持っていたが、deprecatedにされた。それに向けたドキュメントもあるが、主に下位互換性を維持するためのもので、今から作るアプリケーションについて使うべき機能ではない。


まとめ
- OSXにおけるオブジェクトグラフとは、関係性を持ったオブジェクト群の事。
- オブジェクトグラフを保存したり展開したりするには、Archive/Unarchiveの仕組みを使用する。
- Serializationの仕組みは、プロパティリストに保存可能な程度の内容を扱いたい時に限定する。また、自前の型をシリアライズすることは出来ないと心得る。

概要の時点での注意点としては、いわゆる一般に言われるシリアライズ機能というのは、OSXの世界においてはArchiveが担っていて、Serializationの機能は別物であるということくらいだろうか。