[Swift]バックグラウンドでも処理を続ける方法

アプリの処理をバックグラウンドにいっても続ける必要があったので、メモをします。

実行環境

  • OS X El Capitan‎
  • Xcode 7.3
  • Swift 2.2
  • iPhone 6Plus (iOS9.2)

UIBackgroundTaskIdentifierの登録

通常、NSTimerなどの継続して動く処理でもアプリバックグラウンドに移動した瞬間に処理が止まってしまいます。
しかし、UIApplication- beginBackgroundTaskWithName:expirationHandler:- beginBackgroundTaskWithExpirationHandler:を実行するとバックグラウンド状態でも処理を継続することができます。

iOS4から実装されたマルチタスキングの機能なのだそうです。

ViewControllerに一秒ごとに現在時刻をプリントする処理を書いてみます。

import UIKit

class ViewController: UIViewController {

    var timer = NSTimer()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //バックグラウンドでも実行したい処理
        updating()
    }
    func updating()  {
        if self.timer.valid {
            self.timer.invalidate()
        }
        
        self.timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(updating), userInfo: nil, repeats: true)
        
        print("updating_now_time:" + NSDate().description)
    }
}


このままでは、アプリがバックグラウンド状態になったら、現在時刻のプリントは止まってしまいます。

AppDelegate.swiftで処理を継続する処理を追加したいと思います

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    var backgroundTaskID : UIBackgroundTaskIdentifier = 0

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
        
        return true
    }

    //バックグラウンド遷移移行直前に呼ばれる
    func applicationWillResignActive(application: UIApplication) {

        self.backgroundTaskID = application.beginBackgroundTaskWithExpirationHandler(){
            [weak self] in
            application.endBackgroundTask((self?.backgroundTaskID)!)
            self?.backgroundTaskID = UIBackgroundTaskInvalid
        }
 
    }

    //アプリがアクティブになる度に呼ばれる
    func applicationDidBecomeActive(application: UIApplication) {

        application.endBackgroundTask(self.backgroundTaskID)
    }
    
}


解説

var backgroundTaskID : UIBackgroundTaskIdentifier = 0

UIBackgroundTaskIdentifier型の変数を保持型プロパティとして定義します。
レファレンスUIApplication Class Referenceを見る限り、UIBackgroundTaskIdentifier型はInt型のタイプエイリアスみたいです。

typealias UIBackgroundTaskIdentifier = Int

とりあえず値を0としておきました。なんでも良さそうです。

次に、バックグラウンド状態になる直前に呼ばれる- applicationWillResignActive:メソッドでbackgroundTaskIDプロパティを登録します

    //バックグラウンド遷移移行直前に呼ばれる
    func applicationWillResignActive(application: UIApplication) {

        self.backgroundTaskID = application.beginBackgroundTaskWithExpirationHandler(){
            [weak self] in
            application.endBackgroundTask((self?.backgroundTaskID)!)
            self?.backgroundTaskID = UIBackgroundTaskInvalid
        }
 
    }

バックグラウンドタスクはフォアグラウンドに戻った時には– endBackgroundTask:メソッドを実行して、タスクの終了を知らせないといけないそうです。フォアグラウンドに戻った際に呼ばれるメソッドは- applicationDidBecomeActive:なのでそこで実装します。

    //アプリがアクティブになる度に呼ばれる
    func applicationDidBecomeActive(application: UIApplication) {

        application.endBackgroundTask(self.backgroundTaskID)
    }

まとめ

これでバックグラウンドでもNSTimerの処理が継続されるはずです。
ただ、試したところシミュレーターではUIBackgroundTaskIdentifierの登録をしなくてもバックグラウンドで処理が継続していました。。
なんでだろう。。

とりあえず、実機ではUIBackgroundTaskIdentifierの登録が必要なのでご参考になれば幸いです!

参考