【Swift】楽して実装するバックグランドダウンロード

2016/11/21 白石英知
Share on Facebook0Tweet about this on TwitterShare on Google+0Share on LinkedIn0Share on Tumblr0

backgroundtask
 
こんにちは。ユニトラストの白石です。
皆さんはiOSでバックグラウンドでダウンロードを行う実装を行ったことがありますか?
自分はこの間まで実装をしたことがなかったので、とても難しいことだと思っていました。
ところが、iOSにはNSURLSessionBackgroundクラスといって、タスクを作成すればあとは勝手にやってくれるクラスがあるので簡単に実装できます。
 

サンプルアプリ

サンプルアプリを作成しました。githubで公開しています。
対応OSはiOS9以降です。言語はSwift3.0です。
通信にはNSURLSessionBackgroundがラップされた、Alamofireを使用しています。
 

初期化

まず、ユニークな識別子でセッションを初期化します。それによって、アプリがバックグラウンドに行ったり、アプリが終了してもリクエストが保持されることでダウンロードが継続されます。

let configuration = URLSessionConfiguration.background(withIdentifier: DownloadService.key)
self.manager = Alamofire.SessionManager(configuration: configuration)

 

リクエストの作成

次にリクエストを作成します。
urlにはDL元のURL、destinationには保存先を入力します。実際には、ダウンロード中はcacheディレクトリ以下でファイルの書き込みが行われ、完了後にdestinationにファイルが移動されます。
downloadProgressのクロージャーの中にはダウンロード中の処理を書きます。例えば、progressをUIProgressViewやUILabelに反映させることでユーザがダウンロードの進捗状態を確認できます。
responseのクロージャーの中にはダウンロード完了後の処理やエラーが発生した場合の処理を書きます。

let request = self.manager
    .download(url, to: destination)
    .downloadProgress(closure: {progress  in
        //進捗の処理
    })
    .response(completionHandler: {response in
        //完了後あるいは、エラーの処理
    })

これだけでバックグラウンドでダウンロードを行えます。
 

ダウンロード中の機能を拡張する

一般的なアプリでダウンロードを行うときに「一時停止」・「再開」・「キャンセル」といった機能があると思います。
Alamofireではこれらの機能も基本的に1行で書けます。今回はこれらの機能に加え、「再起動したら再開」という機能を追加してみようと思います。
 

一時停止

ダウンロードを一時停止するときは、

request.suspend()

 

再開

ダウンロードを一時停止から再開するときは、

request.resume()

 

キャンセル

ダウンロードをキャンセルするときは、

request.cancel()

 

再起動したら再開

アプリがバックグラウンドで起動している間は、ダウンロードが継続されます。一方、アプリをホームからスワイプで停止させるとダウンロードは停止します。そのようなとき、アプリが再開したときにアプリが閉じる前に行っていたダウンロードを再開する機能です。
アプリを再起動したときに、アプリを閉じたときのタスクがセッションに失敗したタスクとしてNSErrorが保持されています。
ダウンロードを再開するための設定が書かれたplistがtask.error.userInfoの中にNSDataとして保存されているので、それを引数としてダウンロードを再開します。すぐにダウンロードを再開しない場合は、plistとして保存しておくことも可能です。

public func initManager() {
    let configuration = URLSessionConfiguration.background(withIdentifier: DownloadService.key)
    self.manager = Alamofire.SessionManager(configuration: configuration)
        
    //アプリケーションを閉じたときに途中から再開する
    self.manager.session.getAllTasks(completionHandler: {[weak self] tasks in
        guard let wself = self else {
            return
        }
            
        for task in tasks {
            guard let error = task.error else {
                continue
            }
            
            //キャンセルした場合はエラーコード-999が返ってくる    
            if (error as NSError).code == NSURLErrorCancelled {
                let request = self.manager
                    .download(resumingWith: (((error as NSError).userInfo) as NSDictionary)["NSURLSessionDownloadTaskResumeData"] as! Data, to: destination)
                    .downloadProgress(closure: {progress  in
                        //進捗の処理
                    })
                    .response(completionHandler: {response in
                        //完了後あるいは、エラーの処理
                    })
            }
        }
    })
}
Share on Facebook0Tweet about this on TwitterShare on Google+0Share on LinkedIn0Share on Tumblr0