Cocoa Break Logo Top Soft Develop  脱力空間 Logo
Apple Web Badge
made by mi

概要 翻訳 ソース リンク がらくた

概要 Examples ADC Samples 3rd Parties etc CBOriginals

UserDefaults

以下は、Mac OS X v10.4.10 上の Xcode 2.5 で説明しています。

目次:
1 README.rtf の日本語訳
2 アプリケーション
3 ファイル構成
4 MainMenu.nib
5 Controller クラス
6 まとめと機能追加のアイデア

1 README.rtf の日本語訳

この非常に小さなサンプルは、アプリケーションごと、ユーザーごとの設定の読み書きに対する NSUserDefaults の 2 つのよくある単純な使い方を使しています。

最初の例は、ユーザーデフォルトに対する文字列を読み書きします。起動時に、文字列が(「My String」というキーを使って)ユーザーデフォルトデータベースから取得され、テキストフィールド内に示されます。文字列に対して記録された値がないなら、かわりにデフォルトの値が使われます。ユーザーがテキストフィールド内の値を変更したときはいつでも、新しい値がデータベースに書き出されます。新しい値がデフォルトの値と同じなら、かわりにデータベースから値を消去します。

アプリケーションは明示的にデータベースと同期しないことに注意してください。これは、新しい値がアプリケーションが終了するまで書き出されることがなく、その時に AppKit によって自動的に書き出されることを意味します。ほとんどの場合、この動作は適切です。

2 番目の例は、ユーザーデフォルトデータベース内にウインドウの位置とサイズを記録し、ウインドウがユーザーが最後に残したのと同じ場所に現れることを可能にします。

NSWindow は、これを達成するためのメソッドを提供します。そのため、行う必要があるすべてのことは、ウインドウを識別するキー(この場合「My Window」)です。このキーを提供したら、NSWindow がそれ以外のことの面倒を見ます。ウインドウ位置の記録に加えて、NSWindow はサイズとスクリーンのサイズも記録します。そのため、ウインドウ位置は異なるスクリーンサイズ上では「同じような」場所になります。

上記の例の両方において、環境設定はデフォルトの位置に書き出されます。これは、ユーザーごと、アプリケーションごとのものです。ユーザーは現在ログインしているユーザーです。アプリケーションは現在のアプリケーションで、この識別子は、Info.plist 内で供給されている CFBundleIdentifier キーからもたらされます。これは、Project Builder 内で、アプリケーションターゲットに対しては「アプリケーション設定」タブを通じて設定できます。

2 アプリケーション

サンプルを起動すると次のようになります。

起動直後のサンプル

「2nd」と入力して「Record(記録)」ボタンを押します。終了して起動すると「Default」ではなく、「2nd」と表示されます。ここで再びサンプルを終了します。どこに環境設定があるのかを調べてみましょう。

まず「Xcode」で「グループとファイル」から「ターゲット」>「UserDefaults」を選びダブルクリックするか「情報」ボタンでインスペクタを表示させます。「プロパティ」タブを選んで、アプリケーション情報を見てみます。「識別子」の欄は「com.apple.examples.UserDefaults」となっています。これは、Info.plist 内の「CFBundleIdentifier」キーの値が表示されています(Info.plist を見てもよい)。

ここで、現在のユーザーディレクトリを開き、「ライブラリ」>「Preferences」を見てみます。そうすると、「com.apple.examples.UserDefaults.plist」という「識別子」+「.plist」の名前がついたファイルが見つかります。ここに設定値が保存されています。これをダブルクリックすると、Property List Editor が開きます。2 つの項目が保存されているのがわかります。右上の「Dump」ボタンを押すと、下のビューに XML 形式のテキストになったものが表示されます。それは以下のようなものです。

com.apple.examples.UserDefaults.plist
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>My String</key> // 文字列保存のためのキー <string>2nd</string> // 文字列の内容 <key>NSWindow Frame My Window</key> // ウインドウ保存のためのキー <string>525 651 334 99 0 0 1280 938 </string> // ウインドウ位置とスクリーンサイズ </dict> </plist>

ここで注目するのは、<dict> 以下です。これはディクショナリの XML 形式で、<key> がキーを、その直後がそのキーの値を示しています。よって「My String」というキーで「2nd」(「Default」のかわりに入力した文字列)が、「NSWindow Frame My Window」というキーで「525 651 334 99 0 0 1280 938」が保存されているのがわかります。

2 番目のキーの名前は、後で説明する applicationDidFinishLaunching: メソッド内で「My Window」が与えられていて、その前に「NSWindow Frame 」が追加されたものになっています。このサンプルでは、メソッド内で指定されていますが、Interface Builder 内でウインドウのインスペクタの属性の自動保存名で設定することもできます。

2 番目のキーの値は、前の半分がウインドウのフレーム長方形、後がスクリーン長方形となっています。筆者の環境が 1280 × 960 なのでこうなっています。高さがスクリーンのサイズより小さいのはメニューバーの分です。

Property List Editor を閉じ、もう一度、サンプルを起動させてみます。今度は「3rd」と入力して「Record」ボタンをクリックします。そして「com.apple.examples.UserDefaults.plist」を開いてみます。すると、まだ値が「2nd」のままであることがわかるでしょう。まだファイルとは同期されていません。Property List Editor を閉じて、その後にサンプルも終了します。そして再び「com.apple.examples.UserDefaults.plist」を開いてみましょう。今度は値が「3rd」に変わっています。README.rtf の説明のとおり、終了時に同期が行われていることがわかります。

再びサンプルを起動し、今度は場所を移動させないで、ウインドウの大きさを変えてみます。このサンプルでは、横方向にしか伸びません。MainMenu.nib を開いて、ウインドウのサイズを調べると、最小・最大値が 77 で固定されていることがわかります。再びサンプルを終了させます。「com.apple.examples.UserDefaults.plist」を開くと、ウインドウサイズは以下のようになっています。

com.apple.examples.UserDefaults.plist > NSWindow Frame My Window キーの値
<string>525 651 525 99 0 0 1280 938 </string> // ウインドウ位置とスクリーンサイズ

文字列内の 3 番目の数だけが変わっていることがわかります。この数値の並びは、NSRectdescription と同じものです。

3 ファイル構成

プロジェクトファイルを見てみます。

プロジェクトファイル

注目する必要があるのは、Controller.h と .m、そして MainMenu.nib だけです。

4 MainMenu.nib

MainMenu.nib は、以下のようになっています。。

MainMenu.nib

Controller クラスのインスタンスである Controller には、2 つのアウトレットがあります。MyWindow はウインドウに、MyTextField は、ウインドウ内のテキストフィールドにそれぞれ接続されています。また、(表示してませんが)File's Owner(アプリケーション)の delegate アウトレットは、Controller に接続されています。これにより、Controller インスタンスはアプリケーションの委任となります。

次にアクションですが、ウインドウ内のテキストフィールドからは、右側のボタンの performClick: に接続されています。これにより、リターンを押して入力を確定したときに、ボタンが押されたのと同じ効果を持ちます。また、ボタンからは、Controller の textFieldAction: に接続しています。これは、テキストフィードから入力をとりだして処理するメソッドです。

5 Controller クラス

さて、このサンプルの中心である Controller クラスを見てみましょう。

Controller.h
@interface Controller : NSObject { IBOutlet NSTextField *myTextField; // テキストフィールド IBOutlet NSWindow *myWindow; // メインウインドウ } - (IBAction)textFieldAction:(id)sender; - (void)applicationDidFinishLaunching:(NSNotification *)notification;

Interface Builder で接続されていた 2 つのアウトレットがあるだけです。メソッドは、Interface Builder でボタンから接続されていたアクションと、NSApplication の委任メソッドである applicationDidFinishLaunching: です。これはアプリケーションが起動を終了したときに呼び出されます。Interface Builder で File's Owner の委任に設定していたので、Controller インスタンスにこれが送られることになります。よくawakeFromNib を使って初期化が行われますが、awakeFromNib は、nib ファイルから読み込まれた時に呼び出され、これが呼ばれる順番は決まっていません。そのため、awakeFromNib 内で他のインスタンスにアクセスするのは問題があります。そんな時はこの委任メソッドを使います。この委任メソッドは、起動が終了した後で呼び出されるため、この時点ですでに nib ファイルは読み込まれ、そのインスタンスが初期化されています。

まず、この委任メソッドから見てみましょう。

Controller.m > applicationDidFinishLaunching:
- (void)applicationDidFinishLaunching:(NSNotification *)notification { // 行う最初のことは、ユーザーデフォルトから最後に記録された値を読み込み // テキストフィールドの値として、それを設定することです。 // Interface Builder でアウトレットとして準備されているので、 // テキストフィールド myTextField に対するポインタを持っています。 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; // 1 NSString *val = [defaults stringForKey:myKey]; // キーによる文字列の取得 if (val == nil) val = defaultValue; // 2 ない場合デフォルト値に [myTextField setStringValue:val]; // テキストフィールドに設定 // 行うもうひとつのことは、デフォルトウインドウの位置を // デフォルト内に格納されたいたものにすることです。 // これは、単にウインドウに対してどのキーを探すかを伝えるだけで // 他のことは全部自動で行われます! [myWindow setFrameAutosaveName:@"My Window"]; // 自動保存名を指定 // これでウインドウを表示します //(IB でデフォルトで起動時に表示しないものとして設定していた) [myWindow makeKeyAndOrderFront:nil]; // ウインドウを前面に出しキーにする }

1 では、共有される NSUserDefaults を取得しています。このインスタンスはアプリケーションにただ 1 つだけ存在します。これを取得しようとしたとき、まだ作成されていなければ、自動的に作られるので、あるかどうかは心配する必要がありません。ただ取得するだけで OK です。

取得したユーザーデフォルトに対して、キー myKey を渡して文字列を要求しています。myKey は、Controller.m の最初で定義されています。

#define myKey @"My String"

ユーザーデフォルトに入れられるのは基本的にプロパティリストオブジェクトです。そのため、文字列以外にもオブジェクトなどを入れられます。整数や浮動小数点値や真偽値などを出し入れするための便利メソッドも定義されています。ここでは文字列を取り出しています。

2 では、文字列が取得できたかどうかをチェックしています。たとえば、サンプルが始めて起動されたとき、まだ com.apple.examples.UserDefaults.plist は存在しておらず、ユーザーデフォルト内には何の値も含まれていません。そのため、このチェックは必要です。なければ、デフォルトの値に設定されます。この値は、Controller.m の最初で定義されています。

#define defaultValue @"Default"

これで取得できたにせよ、できなかったにせよ、いずれにせよ val が文字列を指している状態になりました。次の行で、この文字列をテキストフィールドに設定しています。

次にウインドウ位置の保存を設定しています。これは簡単で、単にウインドウに自動保存名を知らせているだけです。これにより NSWindow がユーザーデフォルトと自動的にやりとりして、設定を取得したり、保存します。

そして最後に、ウインドウを表示します。Interface Builder で見てみると、ウインドウの「起動時に表示」チェックボックスは外されています。もしこれがチェックされていたら、ウインドウの位置がまず nib で保存されていた場所にテキストフィールドが空の状態で出現し、その後でテキストが入れられ、別の場所に動くことになり、ユーザーにその過程を見せてしまうことになります。

次に、ボタンが押された時に呼び出される textFieldAction: を見てみましょう。

Controller.m > textFieldAction:
- (void)textFieldAction:(id)sender { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSString *val; // テキストフィールドから新しい値を取得 val = [myTextField stringValue]; // デフォルトの値なら、環境設定からそれを除去する。 // そうでなければ、新しい値に設定する。 // これを行う必要はないが、環境設定をより整理されたものにし、 // この方法でデフォルトの値をより隠されたものにする。 // (変更された場合、古いデフォルト値が残らなくなる。) if ([val isEqualToString:defaultValue]) { // フィールド値がデフォルト値かをチェック [defaults removeObjectForKey:myKey]; // そうなら削除 } else { [defaults setObject:val forKey:myKey]; // 違うなら保存 } // ファイルシステムまたは他の何とも同期しないことに注意する。 // それはアプリケーションが終了したときに起こることになる。 // 明示的な同期が必要は場合があるが、それほど頻繁ではない。 // 不必要な同期は、パフォーマンス上の問題となる可能性がある。 }

単純なので、コメントだけで十分にわかると思います。

6 まとめと機能追加のアイデア

たったこれだけのコードで、環境設定を保存し、取り出すことができます。大規模な環境設定でも基本的な手順は同じです。

このサンプルをさらに深く学ぶには、まずテキストフィールドが変更された時に、即座に同期を行う方法を試してみましょう。これは通常のソフトではありまり必要がありませんが、必要な場合もあります。『Cocoa のためのユーザデフォルトプログラミングトピック』>「デフォルトの使用」>「NSUserDefaults とデフォルトデータベースの同期」を見てください。

いろんな設定値を格納してみるのもいいでしょう。オブジェクトを保存してみます。このあたりについては、『Cocoa のためのユーザデフォルトプログラミングトピック』>「ユーザーデフォルトへの NSColor の格納」を参考にしてください。

Cocoa バインディングを使えば、コードではなく Interface Builder 上の設定で環境設定を扱うこともできます。MainMenu.nib にコントローラーを追加して試してみてもいいでしょう。これについては、『Cocoa のためのユーザデフォルトプログラミングトピック』>「ユーザデフォルトおよびバインディング」を見てください。環境設定パネルを作ってバインディングでつなげたりできます。

NSUserDefaults では、ユーザーごと、アプリケーションごとの環境設定ファイルが作られます。すべてのユーザーに共通の設定など他の形で環境設定を保存したい時はどうすればいいでしょうか。『Cocoa のためのユーザデフォルトプログラミングトピック』>「デフォルトドメイン」の説明を読み、最終的には Core Foundation の CFPreferences を使うことになるでしょう。これは、『Core Foundation のための環境設定プログラミングトピック』で説明されています。


管理人:神吉 秀典 E-mail:puer@ba.wakwak.com