Open13

Swift Concurrencyについて調べる

RyugaRyuga

Actorとは

MainActorは、UIのようなメインスレッドで動くものと理解しているがあっているのか?
Viewプロトコルは、@MainActorがついており、メインスレッドで実行されることが保証される。

@MainActor @preconcurrency public protocol View {

    /// The type of view representing the body of this view.
    ///
    /// When you create a custom view, Swift infers this type from your
    /// implementation of the required ``View/body-swift.property`` property.
    associatedtype Body : View

    /// The content and behavior of the view.
    ///
    /// When you implement a custom view, you must implement a computed
    /// `body` property to provide the content for your view. Return a view
    /// that's composed of built-in views that SwiftUI provides, plus other
    /// composite views that you've already defined:
    ///
    ///     struct MyView: View {
    ///         var body: some View {
    ///             Text("Hello, World!")
    ///         }
    ///     }
    ///
    /// For more information about composing views and a view hierarchy,
    /// see <doc:Declaring-a-Custom-View>.
    @ViewBuilder @MainActor @preconcurrency var body: Self.Body { get }
}

Actorは、よくわからないので調べる。

RyugaRyuga

上記は、Xcode 16のViewプロトコル。
SwiftUI.View プロトコルは Xcode のバージョンによって MainActor の範囲が異なるとのこと。

Xcode 16以上 : View 自体が MainActor
Xcode 15以下 : View.body のみが MainActor

RyugaRyuga

この記事の以下の箇所で、nonisolated キーワードを理解した。

https://qiita.com/temoki/items/582fc15697d71f931c8e#:~:text=%40MainActor 属性のついたクラスや構造体は、そのすべてのメソッド・プロパティが MainActor として扱われます。つまり、 MainActor である UIViewController の viewDidLoad() メソッドも MainActor です。

クラスや構造体に @MainActor がついていたとしても、nonisolated キーワードのついたメソッドやプロパティは MainActor から除外されます
特定のメソッドやプロパティのみに @MainActor を付与することもできます

RyugaRyuga

SwiftZoomin #20を見る

こちらの内容を要約しながら理解する。

https://youtu.be/AUcn2y2jjNs?si=_fyNjme2hDA236sl

重要な登場人物

  • Isolation domain
  • Isolation boundary
  • Sendable
RyugaRyuga

Isolation domain

隔離=Isolationする領域のことをIsolation domainと呼ぶ。
Isolation domainの重要な特性は、一つのIsolation domainのなかでは同時に一つの処理しか実行されないことである。

actorというのは、一つの領域で隔離(Isolation)することでデータ競合を防いでいる。

最も馴染み深いのは、MainActorIsolation domainであり、actorのインスタンスに紐づいたIsolation domainも存在する。

MainActorIsolation domainMain Actorで保護された領域を表す。
Actorの場合は、Actorのインスタンス事にIsolation domainをもつ。同じActorでもインスタンスごとにIsolation domainを持つため、注意が必要。

RyugaRyuga

Isolation boundary

Isolation domainが複数存在しているときのそれぞれのIsolation domainの間の境界のことをIsolation boundaryと呼ぶ。

アプリにおいては、それぞれのIsolation domain間でIsolation boundaryを超えて情報のやり取りをしないといけない場面が多々ある。

RyugaRyuga

Sendable

Sendable 並行にアクセスされても安全な型だけが準拠できるプロトコル
Sendableの特徴は、Sendableに準拠した型の値だけがIsolation boudnaryを超えられる。

non-Sendable

Sendableに対して、non-Sendableな値は、Isolation boundaryを超えることができない。

RyugaRyuga

実行時にデータ競合が起こらないことを確認することをコンパイル時にチェックできるように、型の問題とすることで実現している。

RyugaRyuga

Sendableを体感する

上記のBoxクラスをSendableに準拠させて、並行にアクセス可能にさせてみる。

final class Box: Sendable {
    var value: Int = 0
}

そうすると、次のようにSendableに準拠すること自体がエラーであることがわかる。
Box型のプロパティValueが可変状態のため、並行にアクセスすると危険である旨のメッセージが出る。
Stored property 'value' of 'Sendable'-conforming class 'Box' is mutable

ここで、varではなく、letだったらどうなるか。

final class Box: Sendable {
    let value: Int = 0
}

Boxクラスのエラーは解消される。
valueプロパティが定数になるため、run()メソッド内でbox.balue = -1で書き込みをしていた箇所がコンパイルエラーになる。
この部分を読み込むだけのprint(box.value)とすれば、データ競合を起こさない安全なコードにできる。データ競合は、並行にアクセスする少なくとも1つで書き換えが起こっているときに生じるものであるため、今回のように読み込むだけであれば、データ競合の危険性はない。

func run() {
    let box: Box = .init()

    Task {
        // box.value = -1 // Cannot assign to property: 'value' is a 'let' constant
        print(box.value)
    }

    Task {
        print(box.value)
    }
}
RyugaRyuga

Isolation boundaryを体感する

Sendableに準拠していないnon-SendableBoxクラスがIsolation boundaryを超えるとコンパイルエラーが出る例を示す。

final class Box {
    var value: Int = 0
}


actor A {
    var box: Box?
    func setBox(_ box: Box) {
        self.box = box
    }
}


func run() async {
    let box: Box = .init()
    let a: A = .init() // actor A のインスタンスa
    
   await a.setBox(box) // actor A のインスタンスaにboxを渡す ⇐ boxがIsolation bounadaryを超えて、actor AのインスタンスaのIsolation domainに入る。
// ここで次のエラーメッセージが出る。
// Sending 'box' risks causing data races
    
    print(box.value)
}