iOSでのファイル操作する際、NSFileManager
を使うのですが、それぞれのファイル操作はかなり癖があるAPIで中々扱いづらいです。特にSwiftから使うと違和感が大きいです
ということで!簡単にファイルを扱えるようなNSFileManager
のラッパーライブラリを作ってみました!
SwiftFilePath
導入
CocoaPodsでインストール
CocoaPods
のverisonが0.36
以上の環境でPodfileに以下を記載してpod update
すればOKです!
pod 'SwiftFilePath'
( 注:0.36
はプレリリースのためgem install cocoapods --pre
でCocoaPodsをアップデートしておく必要があります )
Carthageでインストール
Cartfile
に以下を記載してcarthage update
すればOKです!
github "nori0620/SwiftFilePath"
便利さ
利用するにはファイルの上に以下のimport文を書きます。
import SwiftFilePath
import
すると、Path
というクラスが利用できるようになります。
例えば、アプリのDocuments
ディレクトリ以下にあるサブディレクトリだけ(=ファイルを除く)の名前一覧を表示したいとします。
SwiftFilePathを使うと以下の1行だけ書けます!(println抜きだと1行)
// 便利!!!( SwiftFilePathをつかった場合 )
let dirs = Path.documentsDir.contents!.filter({$0.isDir}).map({$0.basename })
println(dirs) // 結果:[VideoDir, TextDir, PhotoDir]
ちなみに上と同様の処理をライブラリを使わずにNSFileManager
を使って書くと以下のようになります。
// つらい!!!( 上と同様の処理を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
オブジェクトが生成されます。
let fooDir = Path("/path/to/fooDir")
// toStringを呼ぶと元の文字列が取得できます
fooDir.toString() // "/path/to/fooDir"
もちろん、パスを表現しているクラスなので、存在しないファイルやディレクトリに対しても作成することができます!
そしてアプリ内から、よく利用する各ディクトリに関しては取得するためのショートカットメソッドを用意しています。
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
オブジェクトを取得できます。便利!
// ドキュメントディレクトリの中の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
オブジェクトを持っています。
// parentで親ディレクトリのPathオブジェクトを取得
jsonFilePath.parent.toString() // "~/Documents/subdir"
jsonFilePath.parent.parent.toString() // "~/Documents"
jsonFilePath.parent.parent.parent.toString() // "~/"
contents
メソッドで、ディレクトリの中身一覧をPath
オブジェクトとして取得できます。
// ディレクトリの中身一覧を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
として扱うこともできます。
for content:Path in Path.homeDir { println(content) }
ファイルの情報の取得
isDir
でフォルダかどうかの判定ができます。
Path.homeDir.isDir // true
Path.homeDir["hoge.txt"].isDir //false
exists
はファイルが存在しているかどうかの判定を返します
// ファイルが存在しているかどうかの取得
Path.homeDir.exists // true
// homeDirの中にfoo.txtというファイルが存在するか?
Path.homeDir["foo.txt"].exists
// homeの中のmyDirディレクトリにbar.txtというファイルが存在するか?
Path.homedir["myDir"]["bar.txt"].exists
basename
でファイル名だけを取得できます
Path.homedir["myDir"]["bar.txt"].basename // bar.txt
ext
はファイルの拡張子を取得できます。
// Documentsディレクトリ内のjsonファイルのみの配列を取得
let allFiles = Path.documentsDir.contents!
let jsonFiles = allFiles.filter({$0.ext == "json" })
attributes
でファイルの属性情報を取得することができます。attributesからアクセスできる全ての要素はこちらの公式ドキュメントを御覧ください。
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
でファイル削除ができます。
// Documentディレクトリにfooディレクトリを作成
let fooDir = Path.documentsDir["foo"]
fooDir.mkdir()
// fooディレクトリの中に空のhoge.txtを作成
let hogeFile = fooDir["hoge.txt"]
hogeFile.touch()
// ディレクトリごと削除
fooDir.remove()
copyTo
/moveTo
はその名の通り、コピーと移動を行います。
// Documentsのfoo.txtをtmpディレクトリにコピー
let fooFile = Path.documentsDir["foo.txt"]
let destination = Path.tmpDir["foo.txt"]
fooFile.copyTo( destination )
readString
/writeString
でファイルのデータを文字列で読み書きできます。
// ファイルへ文字列書き込み
let textFile = Path.documentsDir["hello.txt"]
textFile.writeString("HelloSwift")
// ファイルから文字列読み込み
let text = textFile.readString()! // HelloSwift
readData
/writeData
でファイルへデータを書き込めます
// 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
で失敗成功を取得することができます。
例えば以下のようにエラーハンドリングができます。
let result = Path.documentsDir["subdir"].mkdir()
if( result.isSuccess ){ // 成功時
println( result.value! ) // valueには作成できたフォルダのPathが入っている
}
if( result.isFailure ){ // 失敗時
println( result.error! ) // errorにはエラーメッセージが文字列で入っている
}
ただ、上の方法はOptional Value
を!
でアンラップしているのが若干気持ちわるいです。そこで以下のような書き方も用意しています。(結果は上の処理と全く同じです )
Path.documentsDir["subdir"].mkdir()
.onSuccess({ (value:Path) in // 成功時
println( value )
})
.onFailure({ (error:String) in // 失敗時
println( error )
})
こちらの書き方だとOptionalValueを強制的にアンラップせずにエラーハンドリングが書けます。
まとめ
以上、SwiftFilePath
の紹介でした! NSFileManager
をそのまま使うのに比べ、わかりやすいAPI
で様々なことができると思います。 Swiftでファイル操作していてNSFileManagerつらい!となったときは、是非試してみてください!