0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

@MainActor / async await / Task / actor / Sendable をふわっと理解から整理する

Last updated at Posted at 2025-09-30

概要

本記事は、MIXIのminimo負債返済チームでのインターン中に Swift Concurrency を触っていて
「多分こうだろうな」と思いながら使っていた部分やよく見かけるものを、自分の備忘録として整理したものです。
忘れないためのメモですが、同じように「ふわっとした理解で使っている」人の参考になればうれしいです。

async/await

  • async: 非同期処理を含む関数につけるマーク
  • await: 「結果が返ってくるまで待つ」合図

Task の中で async を使う場合

同期コード(普通の関数や SwiftUI の onAppear など)から async を呼ぶ場合は、
直接は呼べないので Task {} を使って「非同期の世界」に入ります。

func fetchUserName() async -> String {
    return "Erika"
}

Task {
    let name = await fetchUserName()
    print(name)
}

async の中で async を使う場合

すでに async な関数の中では、他の async 関数をそのまま await で呼べます。
非同期処理同士は自然につながるイメージです。

func fetchUserName() async -> String {
    return "Erika"
}

func showUserName() async {
    let name = await fetchUserName()
    print(name)
}

Task

同期コードから async を呼び出すための「非同期処理の入口」。
Task {} の中は async コンテキストになるので、await が使える。

Task {
    await doSomething()
}
print("Taskを作った瞬間に次の処理に進む")

Task は非同期処理の入口

非同期処理の呼び出しをたどっていくと、必ずどこかで Task {} に行き着きます。
同期の世界(ボタン押下や onAppear など)から async の世界に入るための「橋渡し」として使われます。

同期コード ──▶ Task { ... } ──▶ async関数 ──▶ await ...

実際の例(SwiftUI の onAppear から async を呼ぶ)

struct ContentView: View {
    var body: some View {
        Text("Hello")
            .onAppear {
                // onAppear は同期コンテキスト
                // 直接 await は書けない
                Task {
                    let name = await fetchUserName()
                    print("ユーザー名: \(name)")
                }
            }
    }
}

func fetchUserName() async -> String {
    return "Erika"
}

Sendable

Sendable は「この型は並行処理で安全に渡せますよ」という意味を持つプロトコルです。
タスク間でデータをやり取りするときに、データ競合が起きないようにチェックしてくれます。

競合が起こると何がまずいの?

複数のタスクが同時に同じデータを書き換えると、結果が保証されません。
Sendable は「その型をタスク間で安全に受け渡せるかどうか」をチェックします。
このとき、値型(struct)と参照型(class)では扱いが異なります。

class Counter {
    var value = 0
}

let counter = Counter()

Task { counter.value += 1 }
Task { counter.value += 1 }

2 になることを期待していますが、実際には同時アクセスのタイミングによって 1 のままになることがあります。
こうした問題を防ぐために Sendable が導入されています。

Sendable と Actor の違い(ざっくり)

  • Sendable = 型の「安全証明書」
    → 「このデータはタスク間で安全に渡せます」とコンパイル時に保証してくれる仕組み。

  • Actor = 「安全な部屋」
    → 複数タスクから同時にアクセスされても、順番に処理してデータが壊れないように守ってくれる仕組み。

どちらもデータレースを防ぐためのものですが、

  • Sendable は 受け渡し時のルール(静的チェック)
  • Actor は 実際のアクセス制御(実行時制御)
    という違いがあります。

※データレースとは、複数のタスクやスレッドが 同じ変数(共有状態)を同時に読み書きすること で、結果が不定になる現象。

Actor

複数のタスクから同時にアクセスされても、ひとつずつ順番に処理する仕組みを持っています。
そのためデータ競合を防ぐことができます。

普通のクラスだと危険

class Counter {
    var value = 0
    func increment() { value += 1 }
}

let counter = Counter()
Task { counter.increment() }
Task { counter.increment() }

同時に呼ばれると内部状態が壊れる可能性がある(値が飛ぶ)

actor を使うと安全

actor Counter {
    private var value = 0
    func increment() { value += 1 }
    func current() -> Int { value }
}

let counter = Counter()
Task { await counter.increment() }
Task { await counter.increment() }
  • actor の中は「1人ずつ順番待ち」になる
  • そのため同時アクセスしてもデータが壊れない
  • メソッド呼び出し時に await が必要なのは「中に入れるまで待つ」から

その他(私が勘違いしていたこと)

非同期の中に非同期?

最初は「非同期の中でさらに非同期を待つってどういうこと?」と思っていました。
でも実際は API → 画像ロード → UI 更新 みたいに、処理を順番につなげていきます。

Task {
    let users = try await fetchUsers()         // API呼び出し
    let avatars = try await fetchAvatars(users) // さらに非同期処理
    await MainActor.run {
        self.images = avatars                   // UI更新
    }
}

並列 = スレッドがたくさん走っている?

これも最初に勘違いしていました。
「並列だから処理ごとに新しいスレッドが生まれてる」と思っていたけど実際は違います。

  • Swift Concurrency では タスクを効率よく少数のスレッドに割り当てて動かしている
  • スレッドは有限で、OS や Swift がうまくスケジューリングしてくれる

「並列 = スレッドが無限に増える」ではなく、「タスクが同時進行しているように見える」仕組み。

終わりに

MIXIのインターンでたくさん Swift Concurrency を触る中で、
「ふわっと理解していたものが整理されて、知見が増えたな」と実感しています。
今後また見返して役立てたいです。

参考:
https://www.swiftlangjp.com/language-guide/concurrency.html
https://developer.apple.com/documentation/swift/concurrency
https://zenn.dev/koher/articles/swift-concurrency-cheatsheet

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?