はじめに
Swift5.5にて非同期に関する追加が行われ、async/awaitについては以前の記事で紹介しましたが、今回は同じく非同期で活躍するactorについて触れたいと思います。
以前の記事はこちらから。
Swift5.5での非同期処理async/awaitの追加
actor
actorはSwiftコンパイラーにて、actorに伴う制限を静的に適用し、可変データへの同時アクセスを防ぐことができます。それでは実際に使用しながら見ていきたいと思います。
1 2 3 4 | actor TestActor { let name = "aaa" var number: Int = 0 } |
上記のようにactorを作成してみました、実際にコードで呼んでみると
このように値の変更が不可であるnameは何も制約を付けずとも呼び出せるのですが、値の変更が可能なnumberはそのままではエラーになってしまいます。エラーを発生させずにnumberにアクセスするには以下のように非同期でアクセスしなければなりません。
1 2 3 | Task { await print(testActor.number) } |
このようにactorを用いることで、データ競合が起こりうる可能性があるパラメータや、それに伴うメソッドを管理することで、整合性を保とうというわけです。
というわけで、actorにはメソッドも追加できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 | actor TestActor { let name = "aaa" var number: Int = 0 func numberInc() -> Int { number += 1 return number } func numberDec() -> Int { number -= 1 return number } } |
メソッドも同じく、非同期で呼び出さないとコンパイラでエラーが表示されます。
ただそこに気をつければ使用方法は他のasyncメソッドと変わりないです。
どのような動きをするかサンプルを作成しました。
1 2 3 4 5 6 7 8 9 | func testActorNumberInc() async -> Int { async let a = testActor.numberInc() async let b = testActor.numberInc() async let c = testActor.numberInc() print("number=",await testActor.number) await print("SUM=", a + b + c) print("number=",await testActor.number) return await testActor.number } |
上記の処理を実行すると以下のログが出力されます。
1 2 3 | number= 0 SUM= 6 number= 3 |
a,b,cをawaitする前にnumberを出力しているので一旦初期値の0が表示されることを確認できると思います。
actorを使用する際の注意
actorを使用することにより、データの競合が発生する確率を下げれるとは思いますが、データの同期などは自分で行わなくてはなりません。例えば以下のように別スレッドで処理を実行した場合、
1 2 3 4 5 6 | queueOne.async { await testActor.numberInc() } queueTwo.async { print("number=",await testActor.number) } |
どちらのスレッドから開始されるかはわからないため、出力させる値が0なのか1なのかわからないです。処理やデータの構造がこのような形で問題ない作りならば良いですが、スレッドが複雑に動き出した時に予期しないケースが発生することもあります。その際はデータの同期の仕組みを考えたり等、現在の作りと変わらないことは、頭に入れておいたほうが良いと思います。
さいごに
actorを使用することにより、非同期で使用する前提のデータ構造や、それらを操作するメソッドを作成できるので、使用者がコンパイラエラーで同期的に使用するのを防げたり、第三者がコードをみた際もわかりやすくなると思うので、async/awaitを使用する際はactorはセットが必須かなと思いました。