第10章

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

第10章

パスとディレクトリの操作


 

 

 

 

 

 

 

10. 1  この章について

 

10. 1. 1  この章について

 

 この章の「パスとディレクトリの操作」というタイトルはむずかしそうに思えるかもしれませんが、実際にはそれほどむずかしくはありません。この章で行うことは次の2つになります。

 

・カレントユーザのホームディレクトリのパスを取得する

・任意の場所に新しいディレクトリ(フォルダ)を作成する

 

 Macはマルチユーザ対応のコンピュータです。ローカルストレージにユーザごとのホームディレクトリ(ホームフォルダ)を作成してそのフォルダの中にそのユーザのすべてのデータや設定を保存するようになっています。ホームフォルダへほかのユーザはアクセスできないことになっています。例えばユーザが1人であればMyClipのデータはどこに保存しても問題はないでしょう。

 

 しかし複数のユーザ1台のMacを共同で使っている場合はMyClipというアプリケーションはすべてのユーザで使えるようにしておき、MyClipが扱うデータは各ユーザのホームフォルダの中に保存するようにしておけば各ユーザのプライバシーは守られます。しかしこの方法を使うためにはMyClipで扱うデータの保存場所をその時にMacを使っているユーザのホームフォルダ内にする必要があります。このことがこの章のひとつ目の課題である「カレントユーザのホームディレクトリのパスを取得する」ということです。なおカレントユーザとは現在Macを使っているユーザのことを表す言葉です。

 

 次にホームフォルダにデータを保存するといってもホームフォルダの中ならどこでも良いということはないでしょう。それなりにデータを保存するのに適している整理された場所が良いはずです。それには最適な場所が用意されています。それは各ユーザのホームフォルダの中の「ライブラリ」フォルダに用意されている「Application Support」というフォルダです。この章ではこのApplication Supportフォルダに「MyClip」という新しいフォルダ(ディレクトリ)を作成してその中にその時Macを使っているユーザMyClipのデータを保存するようにしたいと思います。そしてこのことがこの章のふたつ目の課題の「任意の場所に新しいフォルダ(ディレクトリ)を作成する」ということになります。

 

 

 前章ではSaveボタンを取り除いて、データを保存する時にはSaveメニューかショートカットキーで行うようにMyClipの操作方法を整えました。またウィンドウをクローズした場合もMyClipに合うようにアプリケーション自体も終了するように変えました。これらのことはすべてユーザがMyClipより使いやすいように行ったことです。しかし今回はユーザが直接には関知しないMyClipの内部的構造を整えるための作業になります。

 

 なおMyClipの終了時に未保存のデータが残っていた場合にはそのデータは自動的に保存されるという前章の設定は、「アプリケーションの終了時に未保存のデータが残っていた場合にはユーザに保存するかどうかを確認する」という形に変えたほうが良いでしょう。しかしそういう形にMyClipを変更することは説明の都合上の理由で第11章の「シートとパネル」で行いたいと思います。

 

 そしてここでは「MyClipの終了時に未保存のデータが残っていても何もせず終了する」というかたちに一旦設定しなおします。というか該当メソッドの実装を一旦無効になるようにして作業をすすめていきたいと思います。未保存のデータは意外とうっかり入力してしまったデータであったり、もしくはうっかりデータを削除してしまって編集済みマークがでているというケースも意外と多いものです。しばらくは「データを編集したらその保存はユーザが責任を持って行う。」というかたちにしておきましょう。そのほうがアプリケーション終了時に自動保存してしまうよりは安全な振る舞いだと思います。また前章で作成したMyClipのテストしていて思ったですがUndo機能がないのは不便です。この章ではText ViewUndo機能を有効になるように設定しましょう。そして「間違ってデータを削除してしまったらUndoで元に戻す」ということにもユーザに慣れてもらうことにいたしましょう。なおText ViewがデフォルトではUndo機能が無効にされてあるのはメモリ消費というコスト面を考えてのことだと思います。

 

 

10. 1. 2  Text ViewUndo機能を有効にする

 

 ではまずText ViewUndo機能を有効にします。MyClip.xcodeprojを開いてXcodeを起動してください。そしてプロジェクトウィンドウ内のMainMenu.xibファイルをダブルクリックしてInterface Builderを起動します。画面が立ち上がりましたらText Viewを選択します。いつものとおり選択が確実に行われたかどうかはインスペクタパネルのタイトルバーで確認するようにします。そしてAttributesタブを選んでAllowsグループの「Undo」にチェックを入れます。

 

 

 

 

Text View Attributes Undo

テキスト ボックス: Attributesタブ

 

 Interface Builderでの作業は以上です。MainMenu.xibファイルを保存してXcodeに戻ります。ここで一度テストしてみましょう。「ビルドして進行」をクリックしてMyClipを実行してみてください。Text Viewの文字の編集を行って「Edit」メニューの「Undo」もしくは command + Zで元に戻せればOKです。またText ViewUndoを有効にした場合は同時に「Edit」メニューの「Redo」(やり直す)も有効になります。ぜひ試してみてください。RedoのショートカットキーはShift +command + Zになります。

 

 

10. 1. 3  デリゲートメソッドを無効にしておく

 

 次にアプリケーション終了時(ウィンドウ閉鎖時)に未保存データが残っていた場合にはデータを保存するように実装したwidnowWillCloseデリゲートメソッドの保存に関する式を無効にしてしまいましょう。この項で変更するコードファイルはController.mだけです。いつものとおり強調箇所が追加・変更を行うコードです。

 

Controller.m

 

- (void)windowWillClose:(NSNotification *)notification

{

  //if ([window isDocumentEdited])

  // [self writeToFile:self];

  [NSApp terminate:window];

}

 

 分かりやすいようにコメント化で無効にしていますが実際には削除してもらって構いません。コーディングが終わりましたら「ビルドして進行」をしてみてください。Text Viewの文字列に変更を加えてMyClipを再起動しても変更は反映されていません。今度度はText Viewの文字列に変更を加えて保存コマンドを実行してからMyClipを再起動する変更が反映されています。これで下準備は終わりました。次節ではMyClipユーザのホームフォルダ(ホームディレクトリ)のパスを取得するコードなどを追加していきます。

 

 

 

 

10. 2  パス操作NSHomeDirectory()

 

 

10. 2. 1  ユーザのホームディレクトリのパスを取得する

 

NSHomeDirectory()

 

 今のようにデータが起動ディスクのルートに保存されているというのは変です。第一にユーザが切り替わっても誰からでも見られてしまいます。一般的にデータはユーザのディレクトリのどこかに保存されます。デスクトップもユーザのディレクトリの一部です。そこでグローバル変数filePathを次のように設定するとユーザの「ライブラリ」フォルダの中にMyClip.txtを保存することができます。

 

static NSString *filePath = @"/Users/ユーザのホームフォルダ/Library/MyClip.txt";

 

 しかしこの章の冒頭でも言いましたとおりMacはマルチユーザ対応です。Macを使うユーザが常に同じとは限りません。またMyClipを不特定多数に配布することを考えればユーザのホームフォルダ名がそれぞれの環境で変わることは必至です。そこで用意されている便利な関数がNSHomeDirectory()です。

 

 この関数は戻り値としてその時にMacを使っているユーザ(以後、カレントユーザと呼びます)のルートからホームディレクトリまでのパスをNSString型として返します。たんにカレントユーザユーザ名を返すのではありません。つまり次のような文字列を返します。

 

   "/Users/カレントユーザのホームフォルダ名"

 

 しかしパスとしてはまだ不完全です。どういう名前のファイルを読み込むのか、あるいは書き出す(保存する)のかも分からなければなりません。つまり次のようなパスが必要となります

 

   "/Users/カレントユーザのホームフォルダ名/MyClip.txt"

 

 このような場合NSHomeDirectoryで得られたパスと"/MyClip.txt"という文字列を結合するためにstringByAppendingString:というNSStringのインスタンスメソッドを使うことがですます。

 

   - (NSString *)stringByAppendingString:(NSString *)aString

 

このメソッドはレシーバの末尾に引数の文字列を結合した一時オブジェクトの文字列を戻り値として返します。使用例としては次のようになります。

 

filePath = [[NSHomeDirectory() stringByAppendingString:@"/Library/MyClip.txt"] retain];

 

 レシーバにはNSHomeDirectory()関数を直接指定することができます。戻り値は一時オブジェクトになりますのでretainしています。実際にretainせずに試してみれば分かりますが、上記のメッセージ式をretainなしで読み込みメソッドの直前に記述すると読み込みには成功します。しかし書き込み時(保存時)にはすでにパスはなくなっていますのでアプリケーションは実行時エラーでフリーズ状態になります。なお上記の例ではホームフォルダの「ライブラリ」フォルダの中のMyClip.txtを読み書きするかたちになっています。データの保存先として妥当な場所だと思います。

 

 NSHomeDirectory()で得られたパス(文字列)はリファレンスカウント1としてそのまま残ります。ただしこの文字列にreleaseを送るとエラーが起こります。リファレンスカウンタにはこのように例外事項がけっこうあります。もしあなたが作ったアプリケーションがなぜかうまく動作しないような場合にガーベジコレクションに変更すると正常に動作する場合もあると思います。面倒であればいつでもガーベジコレクションを使ってください。

 

 ガーベジコレクションを使えばメモリ管理に関するコードは一切記述しなくてかまいません。メモリ管理に関するコードが記述されていてもすべて無視されます。また間違ったメモリ管理のコードが記述されていても無視されて正常に動作するようになります。

 

 この基礎編では読者がiPhoneでの開発もされることを念頭においてリファレンスカウンタに慣れていただくためにこの方式でのコーディングを行っています。しかしiPhoneでの開発をされないのであればいつでもガーベジコレクションに移行して頂いてもかまいません。

 

 とは言っても今ここで行っているサンプルアプリケーションはリファレンスカウンタ方式で正常に動作しているわけですし、逆にガーベジコレクションを使ってくださいと言われても困りますよね。しばらくはこのまま頑張って続けていきましょう。

 

 
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 話しを戻します。文字列をつなぐメソッドとしてはパスをつなぐために用意されているメソッドもあります。

 

   - (NSString *)stringByAppendingPathComponent:(NSString *)aString

 

 このメソッドも戻り値は一時オブジェクトになります。さきほどの結合メソッドとの違いはレシーバの末尾か引数の先頭にディレクトリ階層を表すセパレーター「/(スラッシュ)」が抜けていたり、逆に二重に記述してしまっていても自動的に補完してくれるということです。

 

filePath = [[NSHomeDirectory() stringByAppendingPathComponent:@"Library/MyClip.txt"]

   retain];

 

 引数の中のLibraryという文字の前にディレクトリ階層を表す / (スラッシュ)が抜けています。しかしこのメソッドが自動補完を行ない正常に機能します。気になる読者は試してみてください。

 

 この項では上記の2つのメソッドのうち最初のstringByAppendingStringを使うことにいたします。このメソッドはNSStringのメソッドの中で代表格のひとつになります。なお作業に入る前に前節のプロジェクトのバックアップをとっておかれることをおすすめします。私の場合は「MyClip 9.1」というフォルダ名でバックアップを作成しました。また今回も変更されるコードファイルはController.mだけになります。いつものとおり強調箇所が追加・変更されるコードになります。

 

Controller.m

 

  #import "Controller.h"

  static NSString *filePath;

 

今回はグローバル変数の定義時に同時に行っていた初期化コードを削除しています。

 

  - (id)init

  {

      self = [super init];

      if (self) {

       model = [[Model alloc] init];

       filePath = [[NSHomeDirectory()

           stringByAppendingString:@"/Library/MyClip.txt"] retain];

       [self readFromFile];

     }

      return self;

  }

 

initメソッドに強調箇所のコードを追加しています。このコードでグローバル変数を初期化しています。なおこのコードは続く[self readFromFile];メッセージ式の前に記述しておかなければならないことはお分かりいただけるものだと思います。もし分からないようであればreadFromFileメソッドも確認しておきましょう。またこの項の最初のほうでも説明しましたがこのfilePathグローバル変数を初期化するメッセージ式全体をretainし忘れた場合には次の[self readFromFile];メッセージ式の時にはまだfilePathグローバル変数にはパス文字列が残っています。しかし次にどこかで保存メソッドwriteToFileを使った時にはfilePathグローバル変数の値はnilになっています。そしてエラーが起こります。

 

 

10. 2. 2  可変クラス NSMutableString

 

 Foundationフレームワークのクラスのなかには不変クラスのサブクラスとして可変クラスを持っているものが多くあります。

 

 不変クラスとは一度初期化するともうその値を変更できないクラスのことです。NSStringも不変クラスです。今まで値を変更できていたように見えていたかもしれませんが、実際には変数に代入されている値を変更したい場合には、新しい文字列オブジェクトを作って前の文字列オブジェクトと入れ替えていただけです。不要となった前のオブジェクトはreleaseで破棄できる場合もありますし、オブジェクト定数のようにそのままメモリに残る場合もあります。

 

 一方、可変クラスとは初期化後もその値を変更できるクラスのことです。NSStringもサブクラスとして可変クラスとしてNSMutableStringというクラスを持っています。可変クラスはNSというprefix(接頭語)のあとにMutable(変わりやすい)という単語を入れてクラス名にするように決まっています。この可変クラスを使うと変数に代入されているオブジェクトを入れ替えるのではなく、そのオブジェクト自身の値を変更できます。

 

 なぜこのように不変クラスと可変クラスがあるのかというとまたコストの話しになります。可変クラスは不変クラスを継承して拡張したものです。すなわちクラスの仕様が大きくなっています。そしてオブジェクトは大きくなればなるほどリソースを必要とします。コスト面から考えると可変オブジェクトを使う必要がない場合は不変オブジェクトを使ってくださいということになります。このような理由により不変と可変の2つのクラスが用意されています。

 

 次項ではパスを表すグローバル変数に可変クラスのNSMutableStringを使って新しいディレクトリ(フォルダ)を作る方法を説明いたします。可変クラスを使うことによってコストは上がりますが、パスを作るためのコード数は減らせますので次節の学習目標であるディレクトリ作成までの過程は読みやすくなると思います。また余分なオブジェクト定数の数が減りますのでコスト面でどちらが得かは微妙なところだと思います。

 

 また、学習のためにコストのことを何度も言っていますが、MyClipはコストに神経質にならなければいけないような規模のアプリケーションにはまだなっていません。しかしコストやリソースについて常に神経を配ることは良いことです。

 

 

 

 

10. 3  新しいディレクトリを作成する

 

10. 3. 1  Application Supportフォルダに新しいディレクトリを作成する

 

 作業を始めるまえに前節のバックアップをとっておきましょう。毎回言うのもしつこいのでこれからはできるだけ言わないようにしますが、節単位でバックアップをとっていけば困ることはないだろうと思います。実際にみなさんにダウンロードしていただけるようにサイトに用意しているプロジェクトも節単位になっています。

 

 前節ではMyClip.txtをカレントユーザのホームディレクトリの「ライブラリ」フォルダから読み書きするようにしました。MyClipユーザがデータファイルをいくつでも自由に作れるような設計ではなく、固定されたひとつのデータファイルだけを扱う設計になっています。したがってデータの保存場所としてライブラリフォルダは妥当だと思います。このライブラリフォルダにはMac OS X付属のスティッキーズというアプリケーションのデータも保存されています。

 

 このようにデータの保存場所をユーザが選べない形にしたアプリケーションにとって、もうひとつの保存場所の候補として同じくユーザディレクトリのライブラリフォルダの中にある「Application Support 」というフォルダがあげられるでしょう。この節ではこのApplication Supportフォルダの中に「MyClip」というフォルダを作成し、その中にMyClip.txtを保存しようと思います。

 

 このディレクトリの作成にはNSFileManagerというクラスを使います。このクラスの目的はファイルとディレクトリ(フォルダ)の作成、移動、コピー、削除などを行うことです。このクラスにはインスタンスを作成するためのinit○○やコンビニエンスコンストラクタはありません。

 

 前節ではNSStringのメソッドによってファイルが作成されましたが、本来はファイルやフォルダの管理は各アプリケーションが行っているのではなく、OSのファイルシステムによって行われています。NSFileManagerクラスはCocoaアプリケーションとこのファイルシステムとの橋渡しをしているものだと考えてください。

 

 話しばかりでは難しく感じてしまうだけでしょう。さっそくコーディングしていきましょう。今回のコーディングの対象となるのもやはりController.mだけです。そして変更箇所はグローバル変数の定義部分とinitメソッドだけです。いつものとおり強調箇所が追加・変更するコードです。今回は少し長いコードです。

 

Controller.m

 

#import "Controller.h"

static NSMutableString *filePath;

 

@implementation Controller

 

- (id)init

{

     self = [super init];

     if (self) {

    model = [[Model alloc] init];

    filePath = [[NSMutableString alloc] initWithCapacity:0];

    [filePath appendString:NSHomeDirectory()];

    [filePath appendString:@"/Library/Application Support/MyClip"];

    [[NSFileManager defaultManager] createDirectoryAtPath:filePath

       withIntermediateDirectories:YES attributes:nil error:NULL];

    [filePath appendString:@"/MyClip.txt"];

    [self readFromFile];

     }

    return self;

}

 

 

●実行

 

 コーディングが終わりましたら実行するまえにカレントユーザの「ホーム」フォルダ→「ライブラリ」→「Application Support」を開いて表示をリストにしておきましょう。

 

Application Support1

 

 では「ビルドして進行」をクリックして実行してください。MyClipアプリケーションウィンドウが起動すると同時に「Application Support」 フォルダの中に「MyClip」フォルダが出来上がります。

 

 出来上がったMyClipフォルダの左側にある三角形(ディスクロージャー)をクリックして下向き矢印に変えておきます。

 

図:Application Support2

 

MyClipウィンドウに何か文字を入力して保存します。「Application Support」フォルダの「MyClip」フォルダの下の階層に「MyClip.txt」が作成されます。

 

 おめでとうございます。これでおそらくMyClipアプリケーションにとって最も居心地の良いデータ保管所が出来ました。今後、章が進んでMyClipのデータ構造が変わっても保存場所としてはこの場所を使い続けたいと思います。

 

図:Application Support3

 

 起動ディスクのルート(第一階層)やライブラリフォルダに作成されているMyClip.txt使いたい場合はこのApplication SupportフォルダのMyClipフォルダのデータと入れ替えたり、上書きしてもらってもかまいません。また 起動ディスクのルートやライブラリフォルダに作成されているMyClip.txtは削除してもらってもかまいません。ただしこの節より前のMyClipを起動させた場合には何もデータの表示されてないウィンドウが立ち上がることになります。そしてそこにデータを記入して保存するとまた起動ディスクのルートやライブラリフォルダにMyClip.txtが作成されることになります。

 

 

●コード説明

 

グローバル変数

 

   static NSMutableString *filePath;

 

 不変クラスのNSStringから可変クラスのNSMutableStringに変更しています。

 

 

initメソッド

 

 Modelオブジェクトのインスタンス化メッセージとreadFromFileメッセージの間が新しい5つの式で置き換えられています。

 

   filePath = [[NSMutableString alloc] initWithCapacity:0];

 

 NSMutableStringallocして、このクラスで最も一般的な初期化メソッドであるinitWithCapacity:で初期化しています。引数には文字列が何文字まで受け入れるかを整数値で指定します。しかしもともと可変クラスなのでこの値はあとから文字列が変更された場合に自動で変更されます。最初に入れるべき文字列がなければ上記のように最小値の0にしておくと良いでしょう0文字数は空文字を設定したのと同じ意味になります。オブジェクトが存在しないという意味にはなりません。なおこの引数もNSUInteger型が指定されています。

 

   [filePath appendString:NSHomeDirectory()];

 

 appendString:メソッドはレシーバの末尾に引数の文字列を追加するメソッドです。不変クラスNSStringstringByAppendingString:レシーバの文字列の末尾に引数の文字列を追加した新しい文字列を作成しますが、この可変クラスNSMutableStringappendStringメソッドは直接レシーバの文字列の末尾に引数の文字列を追加します。新しいオブジェクトが作成されることはありません。引数のNSHomeDirectory()は前節の最初で説明したようにカレントユーザのルートからホームディレクトリまでのパス文字列を返す関数です。filePathは最初空文字で初期化されましたから実質これが先頭の文字列になります。

 

   [filePath appendString:@"/Library/Application Support/MyClip"];

 

 再びappendString:メソッドでレシーバ(カレントユーザのホームディレクトリまでのパス文字列)の末尾にホームディレクトリ以下のパス文字列を追加しています。最後の「MyClip」はこの時点ではまだ存在していませんが次のメソッドで作られることになります。また「MyClip」がファイルとして作られるのかディレクトリとして作られるのかも次のメソッドによって決まります。

 

   [[NSFileManager defaultManager] createDirectoryAtPath:filePath

          withIntermediateDirectories:YES attributes:nil error:NULL];

 

[NSFileManager defaultManager]ではNSFileManagerのインスタンスを作っているように見えますが返ってくる値は通常のインスタンスではなくファイルシステムとの連絡を取り合うためにレシーバの代わりを努めるオブジェクトが返ってきます。このことはあまり難しく考えずに普通のインスタンスと同じように扱ってもらってかまいません。しかしこのNSFileManagerdefaultManagerメソッドの戻り値として返ってきたオブジェクトのリファレンスカウンタの値を0にすることはできません。このオブジェクトにretainメッセージを送るとリファレンスカウンタの値は増えていきます。しかしreleaseメッセージを送った場合はリファレンスカウンタの値は1までは減りますがそこからは何度releaseメッセージを送ってもリファレンスカウンタの値が0になることはありません。したがってこのオブジェクトは破棄することができません。要するにこのオブジェクトはOSに存在するファイルシステムとの橋渡しをするための一種の代理オブジェクトになっていると考えられます。

 

 続くcreateDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:NULLで第1引数のfilePathの最後の文字列MyClipがディレクトリとして作成されることになります。基本的にはこれから作成しようとするディレクトリがすでに存在していてはいけないことになっていますが第2引数のwithIntermediateDirectoriesYESにしておくと大丈夫ということになっているみたいです。第3引数のattributesはこれから作成するディレクトリのパーミッションなどを指定するために使いますが引数としてnilを渡せばデフォルトの設定でディレクトリが作成されます。最後のerror引数にはエラーメッセージを受け取る変数を指定しますがエラーメッセージが不要な場合は引数としてNULLを渡します。

 

   [filePath appendString:@"/ MyClip.txt"];

 

さきほどのメッセージ式でディレクトリはすでに作成されました。ここで三たびグローバル変数filePathappendStringメソッドを送ってさらにMyClip.txtというファイル名をパスに追加しています。そしてこれ以降はfilePathの文字列が変更されることはなく、読み込み、書き込みの際のファイルパスとして使われることになります。

 

 お疲れ様でした。これで第9章「パスとディレクトリの操作」を終わりたいと思います。次章ではMyClipが扱うデータを画像も添付できるリッチテキスト形式に変更したいと思います。

 



This site is available in Safari and Snow Leopard. (c) viva Cocoa 2006 - 2010