dasukoの技術ブログ

現役エンジニアのブログです。

【Swift】Swiftでコルーチン使ってみた(SwiftCoroutine)

はじめに

今回はSwiftでSwiftCoroutineというライブラリでCoroutineを使ってみたいと思います!

ライブラリのリポジトリはこちらです!

github.com

iOSmacOSなどでの動作を保証する最初のコルーチンライブラリみたいですね!

コルーチンとは

サブルーチンがエントリーからリターンまでを一つの処理単位とするのに対し、コルーチンはいったん処理を中断した後、続きから処理を再開できる。接頭辞 co は協調を意味するが、複数のコルーチンが中断・継続により協調動作を行うことによる。 Wikipediaより引用

コルーチン - Wikipedia

要するにasync/awaitパターンを使って処理を中断したり、再開したり、ある処理が終了するまで待機するといったことを実装するためのプログラミング構造です^ ^

SwiftCoroutineの特徴

  • ロックフリー(ロック不要)
  • スレッドをブロックすることなく待機処理が実装できる
  • パフォーマンスに優れ、かなり高速

プロジェクトへ導入

今回はCocoaPodsを使って導入してみます。

プロジェクトフォルダ内で

pod initを実行します。

 

Podfile内に↓を追加します。

  pod 'SwiftCoroutine'

 

pod installします。

*.xcworkspaceが出力されるので、それを開きます!!!!

※CocoaPodsの使い方については、時間があったら記事書きます!

使ってみる

まずはインポートですね!

以下を追加するとインポートできます!

import SwiftCoroutine

Coroutine

↓これでメインスレッドでコルーチンを実行できるみたいです!

DispatchQueue.main.startCoroutine {
}

 

まずはコルーチンの中でURLSessionを使ってみました!

DataResponseを取得し、responsedataを取得してみました!

import SwiftCoroutine

...

    func doSomething() {
        print("start")
        DispatchQueue.main.startCoroutine {
            
            let future = URLSession.shared.dataTaskFuture(for: URL)
            
            let dataResponse = try future.await()
            let response = dataResponse.response
            let data = dataResponse.data
            
            print("finish")
        }
    }

結果は・・・・

responsedataも正常に取得できました!

ちゃんと待機できてますね!

 

時間も含めたログはこんな感じです。

リクエストに1秒かかっていて、完了を待機できていることが分かりますね

14:05:33 start
14:05:34 finish

 

Future 

続いてもう少し、待機していることがわかりやすいサンプルを実行してみたいと思います!

以下のような関数を定義して実行してみました!

CoFutureは待機ができるオブジェクトで、ジェネリクスで引数の型を指定しています

[CoFuture].await()でfutureが終了するまでコルーチン内で待機できます

今回作成したfutureは5秒後に1を返すfutureです!

 func doSomething() {
        let future: CoFuture<Int> = DispatchQueue.global().coroutineFuture {
            try Coroutine.delay(.seconds(5))
            return 1
        }
        
        DispatchQueue.main.startCoroutine {
            let result: Int = try future.await()
            
            print("result:\(result)")
        }
        print("finished.")
    }

理想としては、コルーチンの中で5秒待機するので、

finishというログが出力され、その5秒後にresult:1が出力されてほしいですね

 

結果は・・・・

14:34:27: finished.
14:34:32: result:1

 

期待通り、finishが出力された5秒後にresult:1が出力されました^ - ^

SwiftCoroutineめっちゃ便利(^○^)

 

ちなみにロック不要なのでこんな処理も簡単に実装できちゃいます!

以下のサンプルでは二つのfuture(future1とfuture2)の合計を出力しています。

future1はglobal queue上で実行され、5秒待機後に1を返します。

future2はmain queue上で実行され、3秒待機後に2を返します。

これら二つのfuture(タスク)を並列に実行してみます!

 func doSomething() {
        let future1: CoFuture<Int> = DispatchQueue.global().coroutineFuture {
            try Coroutine.delay(.seconds(5))
            return 1
        }

        let future2: CoFuture<Int> = DispatchQueue.main.coroutineFuture {
            try Coroutine.delay(.seconds(3))
            return 2
        }

        DispatchQueue.main.startCoroutine {
            let result: Int = try future1.await() + future2.await()
            
            print("result:\(result)")
        }
        print("finished.")
    }

期待される結果はどうなるでしょうか?

future1とfuture2のうち、待機時間が長いのは5秒待機するfuture1なので、

finishedというログが出力されてから5秒後にresult:3というログが出力されてほしいですね

 

結果は・・・

14:40:22 +0000: finished.
14:40:27 +0000: result:3

 

期待通りfinished.のログが出力されてから5秒後にresult:3というログが出力されました(^○^)

Promise

Promiseを使うことで待機後に値を返却することができます。

次のサンプルでは、makeIntFuture関数でPromise(CoPromise)を使って1秒後に10を返しています。

    func makeIntFuture() -> CoFuture<Int> {
        let promise = CoPromise<Int>()
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            promise.success(10)
        }
        
        return promise
    }

    func doSomething() {
        DispatchQueue.main.startCoroutine {
            let result = try self.makeIntFuture().await()
            
            print("\(Date()): result:\(result)")
        }
        print("\(Date()): finished.")
    }

 

期待される結果としては、finishedというログが出力された1秒後にreuslt:10と出力されることです。

結果は・・・

 

14:50:29: finished.
14:50:30: result:10

 

期待通りfinishedというログが出力された1秒後にreuslt:10と出力されました!

 

 

FuturePromiseを使うことで簡単にコルーチン間で値をやり取りすることができます!

最後に

他にもChannelScopeといった機能がありますが、今回は割愛します。

いかがだったでしょうか?  

SwiftCoroutineはかなり便利だと思います^ ^

ぜひ使ってみてください!

 

 

参考になったという方や、ここ間違っているよ!という方は

ぜひコメントしていただけると嬉しいです!!!!

 

Swiftを一から極めたいという人は本を買ってみるといいかも!

Swiftを本気でやろうとしていない人にはお勧めしません。

 

 

参考

GitHub - belozierov/SwiftCoroutine: Swift coroutines for iOS, macOS and Linux.

コルーチン - Wikipedia