【Swift】actorとActor属性(attribute)の違い

Swift

SwiftのActorはactorとして宣言する方法とActor属性でマークする方法があります。
この2つの使い方では様々な違いがあります。

ActorはActorインスタンス毎に排他制御されます。
まずは分かりやすくactorで宣言した場合です。

actor ActorSample:ObservableObject{
    var count = 0
    
    func countUp(){
        sleep(5)
        count += 1
        print(count)
    }
}

func testFunc(){
    let actor1 = ActorSample()
    let actor2 = ActorSample()
    Task{
        await actor1.countUp()
    }
    Task{
        await actor2.countUp()
    }
}

この場合は共にActorSampleのactorを使っていますが、
インスタンスはそれぞれ別のものを使用しています。
その為、この2つのcountUpは並列で実行されます。

次にActor属性でマークした場合は見てみましょう。

@globalActor
actor ActorDifference {
    static var shared = ActorDifference()
}

@ActorDifference
class ActorMarkedClass {
    var count = 0
    
    func countUp(){
        sleep(5)
        count += 1
        print(count)
    }
}

func markedTestFunc(){
    Task{
        let class1 = await ActorMarkedClass()
        await class1.countUp()
    }
    
    Task{
        let class2 = await ActorMarkedClass()
        await class2.countUp()
    }
}

この場合はクラスのインスタンスは共通ですが、
actorのインスタンスはActorDifference.sharedが参照される為、同じになります。
その為、2つのcountUpは排他で実行されます。

また、Actor属性でマークした場合はイニシャライザも非同期で実行する必要があります。

この様にactorで宣言するかActor属性でマークするかで挙動が変わって来ます。
排他制御すべき内容を考え、どちらを使用するかしっかりと判断しましょう。

余談ですが、今回はActorDifference.sharedをvarで宣言したのでこんな例も用意しました。

func markedTestFunc2(){
    Task{
        let class1 = await ActorMarkedClass()
        await class1.countUp()
    }
    Task{
        ActorDifference.shared = ActorDifference()
        let class2 = await ActorMarkedClass()
        await class2.countUp()
    }
}

Actorのインスタンスを変えてみました。
これで排他制御されずに並列実行されます。

試して見たところ、TaskとTaskの間で行うと排他制御されました。
勿論Task内で変えないといけない訳ではなく、以下の例では問題ありませんでした。

Button("CountUp1"){
    Task {
        await actorMarkedClass.countUp()
    }
}

Button("CountUp2"){
    ActorDifference.shared = ActorDifference()
    Task {
        await actorMarkedClass2.countUp()
    }
}

挙動が把握しきれていないので、
Actor属性を定義する時はletで定義する方が無難でしょう。
どうしてもこの様な処理を行う場合はしっかり確認しましょう。

コメント

タイトルとURLをコピーしました