Learn Swift / viva Cocoa / viva Cocoa


Learn Swift / サンプルプログラム  Memo Pad 6


このコーナーでは、Swift による、Mac OS X アプリケーションの作成方法を、説明しています。

掲載開始日、2016年02月13日
最終更新日、2015年02月14日

home  目次  前へ  次へ  mail


ファイル入出力

 現在の Memo Pad は、終了すると、すべてのメモが消えてしまいます。ここでは、Memo Pad が終了するときに、メモをデータ化して、ハードディスクへ保存し、そして起動するときには、そのデータを読み込み、メモを復元するようにします。このように、データをハードディスクに保存 (書き込み) したり、ハードディスクからデータを読み込む技術のことを「ファイル入出力」といいます。


エンコーダーとデコーダー

 Memo Pad のデータは文字列です。このような場合、データを文字列 (テキスト) のまま保存する方法と、シリアライズ (serialize、バイト列化) して保存する二通りの方法があります。シリアライズ (バイト列化) とは、データを2進数に変換することです。今回は、本格的に、シリアライズして保存する方法を選びます。

 データをシリアライズするには、エンコーダーとデコーダーと呼ばれるものが必要です。エンコーダー (encoder) は、データをシリアライズする機能です。デコーダー (decoder) は、シリアライズされているデータを元の形に復元する機能です。

 ここでは、Model.swift にエンコーダーとデコーダーを実装します。Model.swift をエディタエリアに表示させて、次のリストのコードを追加してください。太字部分が追加するコードです。冒頭のコメント部分は省略しています。

import Cocoa

@objc(Model)
class Model: NSObject, NSCoding {
    var note:AnyObject = String()
    
    override init() {
        super.init()
    }
    
    required init?(coder aDecoder: NSCoder) {
        note = aDecoder.decodeObjectForKey("note")!
    }
    
    func encodeWithCoder(aCoder: NSCoder) {
        aCoder.encodeObject(note, forKey: "note")
    }
}
class Model: NSObject, NSCoding
Model クラスに、NSCoding プロトコルを採用しています。これにより Model クラスのプロパティのデータを、シリアライズ (serialize、バイト列化) するコードと、シリアライズされたデータを元に戻す (復元する) コードを記述できるようになります。逆に言えば、NSCoding プロトコルの採用によってこのクラスには、シリアライズに関すコードを必ず記述しなければいけないことになります。もし、このクラスに、シリアライズに関するコードを記述しなければ、コンパイルエラーになります。なお、データをシリアライズ (バイト列化) することをエンコード (encode) といい、シリアライズされたデータ(バイト列)を元のデータに戻すことをデコード (decode) と言います。ここでは、Model クラスに note プロパティのデータをシリアライズするためのエンコーダーメソッドと、シリアライズされたデータを復元するためのデコーダーメソッドを記述します。

override init() { super.init() }
通常、すべてのプロパティに初期値がある場合は、init() メソッドは省略することができます。しかしここでは、NSCording で指定されている デコーダーの init?() メソッドも記述しなければいけません。その関係で、この init() メソッドを省略することはできません。

required init?(coder aDecoder: NSCoder) {
デコーダーメソッドです。2014年の Swift 登場以来、さまざまなデコーダーが使われてきたと思います。しかし、2016年2月現在、Xcode 7.2.1 でコンパイラを通るのは、このデコーダーメソッドだけではないかと思います。このメソッドは、Apple のクラスリファレンスに掲載されています。

note = aDecoder.decodeObjectForKey("note")!
デコーダーの実装です。行末に「!」を記述してアンラップ (非オプショナル化) している以外には、変わったところはありません。

func encodeWithCoder(aCoder: NSCoder) {
エンコーダーです。特に変わったところはないと思います。

aCoder.encodeObject(note, forKey: "note")
エンコーダーの実装です。特に変わったところはないと思います。

 エンコーダーとデコーダーの中で、実際にどのような処理がされているかは、Apple によってブラックボックス化されています。

 これで、Model.swift に、note プロティのデータをシリアライズするコードと、note プロパティのデータを復元するコードの記述が完了しました。しかしこれでハードディスクへのデータの保存と、ハードディスクからのデータの読み込みができるようになったわけではありません。エンコーダーは、データを保存する前に、データをシリアライズするためのコードです。そして、デコーダはハードディスクから読み込んだデータを、元のデータの形に復元させるためのコードです。データの保存と読み込みは、また別途記述しなければなりません。


NSKeyedArchiver と NSKeyedUnarchiver

 ハードディスクへのデータの保存 (書き込み) には、NSKeyedArchiver クラスのメソッドを使います。ハーディディスクからのデータの読み込みには、NSKeyedUnarchiver クラスのメソッドを使います。ところでこのような「ファイル入出力」のためのコードはどこに記述すれば良いのでしょうか。MVC によると、ファイル入出力」のためのコードは、コントローラクラスに記述するように指針が出ています。しかし、ArrayController には、コードを記述できる場所がありません。そこで今回は、ViewContoroller に「ファイル入出力」のためのコードを記述することにします。コントローラは一つである必要ありません。一つのアプリケーションに複数のコントローラが存在することは全く問題ありません。

 ナビゲータエリアで「ViewController.swift」を選択してください。エディタエリアに、ViewController.swift のコードが表示されます。そして、ViewController.swift に次のリストのコードを記述してください。太字部分が追加するコードです。冒頭のコメント部分は省略しています。・・・はコードが省略されていることを意味しています。

import Cocoa

class ViewController: NSViewController {
    
    dynamic var notes:[AnyObject] = []
    var filePath:String = String()
    
    func save() {
        NSKeyedArchiver.archiveRootObject(notes, toFile: filePath)
    }
    
    func read() {
        if let file = NSKeyedUnarchiver.unarchiveObjectWithFile(filePath) {
            notes = file as! [AnyObject]
        }
    }
    
    override func viewWillAppear() {
        super.viewWillAppear()
        
        filePath = NSHomeDirectory() + "/Library/Application Support/viva Cocoa"
        do {
            try NSFileManager.defaultManager().createDirectoryAtPath(
            	filePath,
            	withIntermediateDirectories: true,
            	attributes: nil)
        } catch {
            print("Error")
        }
        filePath += "/Memo Pad.data"
        read()
    }
    
    override func viewWillDisappear() {
        super.viewWillDisappear()
        
        save()
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }

    override var representedObject: AnyObject? {
        didSet {
        // Update the view, if already loaded.
        }
    }


}

dynamic var notes:[AnyObject] = []
AnyObject 型の空の (実際のデータのない) 配列として、notes 変数を定義しています。冒頭の dynamic は、Cocoa バインディングで使われるプロパティに、付けることになっているキーワードです。今まで付けていなかったのは、@objc を代わりに使っていたからです。

var filePath:String = String()
データを保存する場所までのパス (path、道筋) を保持するための、String 型の変数です。

func save() {
保存のためのユーザー定義メソッドです。

NSKeyedArchiver.archiveRootObject(notes, toFile: filePath)
archiveRootObject は、NSKeyedArchiver クラスに定義されているクラスメソッドです。第一引数のデータを、第二引数の場所へ保存 (書き込み) します。

func read() {
読み込みのためのユーザー定義メソッドです。

if let file = NSKeyedUnarchiver.unarchiveObjectWithFile(filePath) {
unarchiveObjectWithFile は、NSKeyedUnarchiver のクラスメソッドです。引数で指定したパスのファイル (データ) を読み込みます。if 文で受けているのは、データがまだない可能性もあるからです。

notes = file as! [AnyObject]
file 引数のデータを、AnyObject 型の配列として割り当てて、notes 変数に代入しています。

override func viewWillAppear() {
ViewControler のスーパークラスである NSViewController で定義されているメソッドです。このメソッドはビュー (つまりウィンドウ) が、表示される直前に呼び出されます。このタイミングでハードディスクからファイル (データ) を読み込むことにします。

super.viewWillAppear()
まず最初に、スーパークラスの viewWillAppear メソッドを呼び出しています。

filePath = NSHomeDirectory() + "/Library/Application Support/viva Cocoa"
NSHomeDirectory() はお使いの Mac の、ホームディレクトリまでのパスを返します。そのパスに /Library/Application Support/viva Cocoa というパスを継ぎ足して、fiePath に代入しています。

do { try NSFileManager.defaultManager().createDirectoryAtPath( filePath, withIntermediateDirectories: true, attributes: nil) }
NSFileManager は、ファイルの管理をするクラスです。defaultManager は、FileManager の唯一のインスタンスです。createDeirectoryAtPath は、引数で指定したパスにディレクトリを作ります。このコードが do try のエラー構文の中に入っているのは、ディレクトリの作成が必ず成功するとは限らないからです。

} catch { print("Error") }
エラーが起こった時の作業を catch 節に記述しています。ここではとりあえず、デバッグエリアに「Error」と表示されるようにしました。

filePath += "/Memo Pad.data"
ディレクトリの作成に成功した後に、パスにデータのファイル名を追加しています。

read()
read メソッドを呼び出しています。

override func viewWillDisappear() {
ViewController のスーパークラスである、NSViewController に定義されているメソッドです。このメソッドはビュー (ウィンドウ) が消える直前、つまりウィンドウが閉じられる直前に呼び出されます。

super.viewWillDisappear()
まずスーパークラスの viewWilDisapear を呼び出しています。

save()
save メソッドを呼び出しています。


Array Controller の設定

 ナビゲータエリアで Main.storyboard を選択して、ドキュメントアウトラインで ArrayController を選択します。

 そして、バインディングインスペクタで Controller Content グループの Content Array を開いてください。次に、Bind to にチェックを入れて ViewController を選びます。Controller Key は空白のままにしておいてください。そして、Model Key Path に「notes」と入力してエンターキーを押します。エンターキーを押すのは入力を確定させるためです。


完成

 これで、Memo Pad は完成しました。実行して色々と試してみてください。ウィンドウを閉じると自動的にメモが保存されます。そして再び Memo Pad を実行すると、前回のメモが読み込まれます。

お疲れ様です。

 これで、Memo Pad を終了します。次のページで何をするかは未定です。


home  目次  前へ  次へ  mail


無断転載禁止、リンクフリー
Copyright 2016. vivacocoa.jp All right reserved.