SwiftでZipファイルをアプリ内にダウンロードする方法

はじめに

iOSアプリ内にZipファイルを保存する際どこに保存するかですが、当初私はDocumentsフォルダに保存しておけば良いと考えていました。しかし、Documentsフォルダには原則としてユーザに関するデータを保存するべき場所でユーザに公開して構わない情報を保存しておくことがガイドラインに記載されています。

ガイドラインにしたがっていない場合リジェクトの対象になるようです。zipファイルはユーザに公開すべきデータではないのでフォルダに保存するとリジェクトされてしまう可能性があるかもしれません。

次に考えたのがCachesフォルダへの保存です。再度ダウンロードまたは再生成できるデータの保存するフォルダとガイドラインに記載がありました。

しかし以下の引用を読むと何らかのタイミングでデータが消えてしまうことがある様です。ユーザに一度ダウンロードしたコンテンツを再度ダウンロードさせるのはユーザを混乱させてしまう恐れがあるので好ましくないと思いました。

iOS 5.0以降では、まれですが、システムのディスクスペースが非常に少ない場合に、Cachesディレクトリが削除される場合があります。このような削除は、アプリケーションの実行中は起こりません。ただし、Cachesディレクトリが消去される状況は、iTunesの復元時に限らないことを覚えておいてください。

https://qiita.com/nnsnodnb/items/13642c4a8d55641f893e

では、どこに保存するかですがLibraryの直下に保存するフォルダを作成しそこにzipファイルを置いておくのが良いと思います。突然消えてしまうことはありませんし、iTunesにバックアップが保存される為復元時にもそのまま利用することができます。

URLSessionDownloadDelegate

SwiftでZipファイルのダウンロードを行う際はURLSessionDownloadDelegateにさまざまな通知を受け取るメソッドが用意されています。

今回サンプルで実装したメソッドは以下の3つです

ダウンロードが完了したタイミングで呼ばれる

func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL)

ダウンロードの進捗

func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)

ダウンロード中のエラー

func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)

サンプル

Swiftでzipファイルを保存するサンプルコードを作成しました。ZipファイルはFirealpacaのダウンロードをサンプルとして使用しています。

コードにコメントを入れておいたので迷うことはないかなと思います。ビルドするとLibrary/Zip下にfirealpacaのデータが保存されています。

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let config = URLSessionConfiguration.background(withIdentifier: "download-zip")
        let session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
        let task = session.downloadTask(with: URL(string: "https://firealpaca.com/download/mac")!)
        print("ダウンロード開始")
        task.resume()
    }
    
    private func createZipFolder(){
        //ライブラリの下にzip保存用のフォルダを作成
        let libraryPath = NSHomeDirectory() + "/Library"
        do {
            if FileManager.default.fileExists(atPath: libraryPath) {
                print("フォルダが既に存在するのでスキップ")
                return
            }
            try FileManager.default.createDirectory( atPath: libraryPath+"/Zip", withIntermediateDirectories: true, attributes: nil)
            print("success")
        } catch {
            //エラー処理
            print("error")
        }
    }
}

extension ViewController: URLSessionDownloadDelegate {
    //ダウンロード完了時に呼ばれる
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
        print("ダウンロード完了")
        let fileManager = FileManager.default
        
        let libraryPath = NSHomeDirectory() + "/Library/Zip"
        let targetURL = URL(fileURLWithPath: libraryPath + "/firealpaca.zip")
        
        //フォルダ内に入っているファイルを削除
        try? fileManager.removeItem(at: targetURL)
        //フォルダの移動
        try? fileManager.moveItem(at: location, to: targetURL)
    }
    
    //ダウンロードの進捗具合
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
        let parcentage = Int((Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)) * 100)
        print("進捗: \(parcentage)%")
    }
    
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        if let err = error {
            print("エラーが発生しました: \(err)")
        }
    }
}

参考文献

FileManager

URLSessionDownloadDelegate

SwiftでiOSアプリのファイルシステムのディレクトリの存在チェックを行う

URLSessionDownloadの仕組みとswiftでのzipファイルの操作に関して

Firealpaca