この記事は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周りぐらいしかないので、おそらく普通は使わないと思いますが、触ってみると面白いですね。
続きは次回へ
