[iOS] ファイル操作が劇的に簡単になる便利ライブラリ！

iOSでのファイル操作する際、`NSFileManager`を使うのですが、それぞれのファイル操作はかなり癖があるAPIで中々扱いづらいです。特にSwiftから使うと違和感が大きいです
ということで！簡単にファイルを扱えるような`NSFileManager`のラッパーライブラリを作ってみました！

## SwiftFilePath

https://github.com/nori0620/SwiftFilePath

## 導入

#### CocoaPodsでインストール
`CocoaPods`のverisonが`0.36`以上の環境でPodfileに以下を記載して`pod update`すればOKです！

```rb:Podfile
pod 'SwiftFilePath'
````

( 注:`0.36`はプレリリースのため`gem install cocoapods --pre`でCocoaPodsをアップデートしておく必要があります )

#### Carthageでインストール

`Cartfile`に以下を記載して`carthage update`すればOKです！

```rb:Cartfile
github "nori0620/SwiftFilePath"
````


## 便利さ

利用するにはファイルの上に以下のimport文を書きます。

```swift:各ファイル
import SwiftFilePath
````

`import`すると、`Path`というクラスが利用できるようになります。
例えば、アプリの`Documents`ディレクトリ以下にあるサブディレクトリだけ(=ファイルを除く)の名前一覧を表示したいとします。
SwiftFilePathを使うと以下の１行だけ書けます！(println抜きだと１行)

```swift:SwiftFilePathを使った場合
// 便利！！！( SwiftFilePathをつかった場合 )
let dirs = Path.documentsDir.contents!.filter({$0.isDir}).map({$0.basename })
println(dirs)  // 結果:[VideoDir, TextDir, PhotoDir]
````
ちなみに上と同様の処理をライブラリを使わずに`NSFileManager`を使って書くと以下のようになります。

```swift:SwiftFilePathを使わない場合
// つらい！！！( 上と同様の処理をNSFileManagerを直接使って作った場合 )
let fileManager = NSFileManager.defaultManager()
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
let contents =   fileManager.contentsOfDirectoryAtPath(documentsPath, error: nil)
let dirs = contents!.filter({ file  in
    let filePath = documentsPath.stringByAppendingPathComponent(file as String)
    var isDirectory: ObjCBool = false
    let isFileExists = NSFileManager.defaultManager().fileExistsAtPath(filePath, isDirectory:&isDirectory)
    return isDirectory ? true : false // ObjcBool -> bool
})
println(dirs)  // 結果:[VideoDir, TextDir, PhotoDir]
````

というわけで`SwiftFilePath`を使うと`NSFileManager`のレガシー感が隠蔽されるのでツラさが減ります。

## 具体的な使い方

### オブジェクトの作成

`Path`クラスに文字列を渡すと、ファイルのパスを表す`Path`オブジェクトが生成されます。

```swift:Pathオブジェクトの作成
let fooDir = Path("/path/to/fooDir")

// toStringを呼ぶと元の文字列が取得できます
fooDir.toString() // "/path/to/fooDir"
````
もちろん、パスを表現しているクラスなので、存在しないファイルやディレクトリに対しても作成することができます！

そしてアプリ内から、よく利用する各ディクトリに関しては取得するためのショートカットメソッドを用意しています。

```swift:アプリから利用できるディレクトリ
Path.homeDir.toString()
// "/var/mobile/Containers/Data/Application/<UUID>"

Path.documentsDir.toString()
// "/var/mobile/Containers/Data/Application/<UUID>/Documents"

Path.cacheDir.toString()
// "var/mobile/Containers/Data/Application/<UUID>/Library/Caches"

Path.temporaryDir.toString()
// "var/mobile/Containers/Data/Application/<UUID>/Library/tmp"
````

### パス操作

`[]`を使ってディレクトリの中の要素の`Path`オブジェクトを取得できます。便利！

```swift
// ドキュメントディレクトリの中のfoo.txtのPathを表すオブジェクトを取得
let textFilePath = Path.documentsDir["foo.txt"]
textFilePath.toString() //  "~/Documents/foo.txt"

// 階層になっていてもアクセスできる
let jsonFilePath = Path.documentsDir["subdir"]["bar.json"]
jsonFilePath.toString() //  "~/Documents/subdir/bar.json"
````

`preant`は親ディレクトリの`Path`オブジェクトを持っています。

```swift
// parentで親ディレクトリのPathオブジェクトを取得
jsonFilePath.parent.toString() // "~/Documents/subdir"
jsonFilePath.parent.parent.toString() // "~/Documents"
jsonFilePath.parent.parent.parent.toString() // "~/"
````

`contents`メソッドで、ディレクトリの中身一覧を`Path`オブジェクトとして取得できます。

```swift
// ディレクトリの中身一覧をPathオブジェクトで取得
let contents = Path.homeDir.contents!
//  Pathオブジェクト化されたhomeディレクトリの中身が配列に入っている
// [
//    Path<~/.com.apple.mobile_container_manager.metadata.plist>, 
//    Path<~/Documents>,
//    Path<~/Library>, 
//    Path<~/tmp>,
// ]
````

さらに！フォルダを指す`Path`オブジェクトは、それ自体を`for in`で`Iterator`として扱うこともできます。

```swift
for content:Path in Path.homeDir { println(content) }
````

## ファイルの情報の取得

`isDir`でフォルダかどうかの判定ができます。

```swift
Path.homeDir.isDir // true
Path.homeDir["hoge.txt"].isDir //false
````

`exists`はファイルが存在しているかどうかの判定を返します

```swift
// ファイルが存在しているかどうかの取得
Path.homeDir.exists // true

// homeDirの中にfoo.txtというファイルが存在するか？
Path.homeDir["foo.txt"].exists

// homeの中のmyDirディレクトリにbar.txtというファイルが存在するか？
Path.homedir["myDir"]["bar.txt"].exists
````

`basename`でファイル名だけを取得できます

```swift
Path.homedir["myDir"]["bar.txt"].basename // bar.txt

````

`ext`はファイルの拡張子を取得できます。

```swift
// Documentsディレクトリ内のjsonファイルのみの配列を取得
let allFiles  = Path.documentsDir.contents!
let jsonFiles = allFiles.filter({$0.ext == "json" })
````

`attributes`でファイルの属性情報を取得することができます。attributesからアクセスできる全ての要素は[こちらの公式ドキュメント](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/#//apple_ref/doc/uid/20000140-SW16)を御覧ください。

```swift
let jsonFile = Path.documentsDir["foo.json"]
jsonFile.attributes!.fileCreationDate()! // 2015-01-11 11:30:11 +0000
jsonFile.attributes!.fileModificationDate()! // 2015-01-11 11:30:11 +0000
jsonFile.attributes!.fileSize() // 2341
````

## ファイル操作

`mkdir`でディレクトリ作成、`touch`で空ファイル作成、`remove`でファイル削除ができます。

```swift
// Documentディレクトリにfooディレクトリを作成
let fooDir = Path.documentsDir["foo"]
fooDir.mkdir()

// fooディレクトリの中に空のhoge.txtを作成
let hogeFile = fooDir["hoge.txt"]
hogeFile.touch()

// ディレクトリごと削除
fooDir.remove()
````

`copyTo`/`moveTo`はその名の通り、コピーと移動を行います。

```swift
// Documentsのfoo.txtをtmpディレクトリにコピー
let fooFile = Path.documentsDir["foo.txt"]
let destination = Path.tmpDir["foo.txt"]
fooFile.copyTo( destination )

````

`readString`/`writeString`でファイルのデータを文字列で読み書きできます。

```swift
// ファイルへ文字列書き込み
let textFile = Path.documentsDir["hello.txt"]
textFile.writeString("HelloSwift")

// ファイルから文字列読み込み
let text = textFile.readString()! // HelloSwift
````

`readData`/`writeData`でファイルへデータを書き込めます

```swift
//  NSDataの書き込み
let binFile = Path.documentsDir["foo.bin"]
binFile.writeData( NSData()  )

// NSDataの読み込み
let data = binFile.readData()!
````

## エラーハンドリングについて

上では触れていませんでしたがファイルシステムについて、副作用を持つメソッド`touch`/`remove`/`copyTo`/`writeTo`などは返り値として`Result`オブジェクトが返ってきます。

`Result`オブジェクトは操作が成功した場合は`value`に結果の値を持ちます。失敗した場合は`error`にエラーメッセージが入っています。
また`isSuccess`/`isFailure`で失敗成功を取得することができます。

例えば以下のようにエラーハンドリングができます。

```swift
let result = Path.documentsDir["subdir"].mkdir()
if( result.isSuccess ){ // 成功時
    println( result.value! ) // valueには作成できたフォルダのPathが入っている
}
if( result.isFailure ){ // 失敗時
    println( result.error! ) // errorにはエラーメッセージが文字列で入っている
}
````

ただ、上の方法は`Optional Value`を`!`でアンラップしているのが若干気持ちわるいです。そこで以下のような書き方も用意しています。（結果は上の処理と全く同じです )

```swift
Path.documentsDir["subdir"].mkdir()
    .onSuccess({ (value:Path) in // 成功時
        println( value )
    })
    .onFailure({ (error:String) in // 失敗時
        println( error )
    })
````

こちらの書き方だとOptionalValueを強制的にアンラップせずにエラーハンドリングが書けます。

## まとめ

以上、`SwiftFilePath`の紹介でした！ `NSFileManager`をそのまま使うのに比べ、わかりやすい`API`で様々なことができると思います。　Swiftでファイル操作していてNSFileManagerつらい！となったときは、是非試してみてください！