LoginSignup
6

More than 5 years have passed since last update.

サブスレッドでアニメーションループを作る

Last updated at Posted at 2017-12-07

この記事はQuadアドベントカレンダー7日目の記事です
前回wift用アニメーションエンジンを作り始めるからの第2回目です。

今回はアニメーション用のスレッドを別に用意して、CADisplayLinkを利用する方法を模索したいと思います。メインスレッドは何かと処理が重なると思うので、サブスレッドに分けてそちらでアニメーション専用で利用すればパフォーマンスも良くなるのではと考えています。

色々調べたところ、どうやらRunLoopを作る必要があるところまではわかりました。

RunLoopを作るには

実行ループを設定するために必要な作業は、スレッドを起動し、実行ループオブジェクトの参照を取得し、イベントハンドラを組み込んで、実行ループに実行を指示することだけです。長時間存続する二次スレッドを作成する場合は、そのスレッド用の実行ループを独自に設定する必要があります。

RunLoopはCADisplayLinkの設定でも使用したと思いますが、サブスレッドを切らない限りはRunLoop.currentでメインスレッドの実行ループを取得できます。
何度実行しても一つのスレッド内では同一のRunLoopを取得します。シングルトンみたいですね。

別スレッド内でRunLoop.currentすると新しいじ実行ループを取得できます。RunLoop.currentのタイミングで新規さ作成されるようです。

実際に作ってみる

RunLoopはThreadに紐づくようです。そのためまずはThreadを作る必要があります。
作るのは簡単です。

override func viewDidLoad() {
        super.viewDidLoad()
        let thread = Thread(
                     target: self, 
                     selector: #selector(self.threadLoop), 
                     object: nil
        )
        thread.start()
}
@objc func threadLoop() -> Void {
    print("thread loop done!")
}

起動するとコンソールに「thread loop done!」が表示されると思います。この処理では、単に別スレッドで処理を実行したというだけのようです。

RunLoopを取得する

threadLoop()内で、RunLoop.currentを実行すると、サブスレッドのRunLoopが取得できます。RunLoopは一つのスレッドに対して、一つだけ存在します。

@objc func threadLoop() -> Void {
    print("thread loop done!")
    let runLoop = RunLoop.current
}

CADisplayLinkを利用する

runLoopが取得できたのでCADisplayLinkを利用してアニメーションをエンジンを組み立てます。

@objc func threadLoop() -> Void {
    print("threadLoop done")
    self.updater = CADisplayLink(target: self, selector: #selector(self.onLoop(_:)))
    updater!.preferredFramesPerSecond = 60
    updater?.add(to: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
}

@objc func onLoop(_ sender:AnyObject ) -> Void {
     print("CADisplay loop done!!")
}

しかしこれでは、onLoop()のprintが実行される気配がありません。上記実装では、threadLoop()が実行されたタイミングで、Threadは終了し、RunLoopも消滅するためです。

スレッド用の実行ループを独自に設定

スレッド〜キープするために、ループ処理を利用します。

@objc func threadLoop() -> Void {
    self.updater = CADisplayLink(target: self, selector: #selector(self.onLoop(_:)))
    updater!.preferredFramesPerSecond = 60
    updater?.add(to: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)

    // スレッドをキープする
    while self.isRunning {
        // ここの数値はFPS設定に影響しています。1/60 = 60FPS
        RunLoop.current.run(until: Date(timeIntervalSinceNow: 1/60))
    }
}

これでサブスレッドはキープされています。コンソール上に「CADisplay loop done!!」が大量に表示されると思います。

Swiftでスレッド周りの情報はGCD周りぐらいしかないので、おそらく普通は使わないと思いますが、触ってみると面白いですね。

続きは次回へ

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6