Swift6-Swift Concurrency Checking対応したい我
Sendableの理解が必要そう
以下のSendableTypeを読んでみる
同時実行ドメイン間で共有できる型は、送信可能型と呼ばれる
同時実行ドメイン間で共有できる型は、送信可能型と呼ばれます。たとえば、アクター メソッドを呼び出すときに引数として渡したり、タスクの結果として返したりできます。この章の前半の例では、同時実行ドメイン間で渡されるデータに常に安全に共有できる単純な値型を使用しているため、送信可能性については説明しませんでした。対照的に、一部の型は同時実行ドメイン間で渡すのが安全ではありません。たとえば、可変プロパティを含み、それらのプロパティへのアクセスをシリアル化しないクラスは、そのクラスのインスタンスを異なるタスク間で渡すと、予測できない誤った結果を生成する可能性があります。 Sendable プロトコルへの準拠を宣言することで、型を送信可能としてマークします。
→Sendableプロトコルをつける理由は、同時実行ドメイン間で共有できる安全な型として宣言するため
型を送信可能にする方法
Sendable プロトコルへの準拠を宣言することで、型を送信可能としてマークします。このプロトコルにはコード要件はありませんが、Swift が強制するセマンティック要件はあります。一般に、型を送信可能にする方法は 3 つあります。
- 値型で、送信可能なデータで構成されている場合
struct MyStruct: Sendable {
var number: Int
var text: String
}
// IntやStringは既にSendableなので、この構造体もSendableになります。
- 不変の状態を持つ場合
struct ImmutableStruct: Sendable {
let number: Int
let text: String
}
// プロパティが全て読み取り専用のため、この構造体もSendableです。
- 可変状態の安全性を保証するコードがある場合
@MainActor
class MainActorClass: Sendable {
var number: Int = 0
var text: String = ""
}
// @MainActor でマークされているため、クラスの状態はメインスレッドでのみ変更されることが保証され、Sendableと見なされます。
暗黙的Sendable
以下は、Sendable プロトコルへの準拠が暗黙的に示される構造体のバージョンです。
struct TemperatureReading {
var measurement: Int
}
型が送信可能ではないことを明示的にマークする
struct FileDescriptor {
let rawValue: CInt
}
@available(*, unavailable)
extension FileDescriptor: Sendable { }
• 拡張の無効化: @available(*, unavailable)を使うことで、FileDescriptorがSendableプロトコルに準拠しているとコンパイラが認識しないようにしています。これにより、他の場所で誤ってSendableとして使われることを防ぎます。
• 明示的な意図の表明: このアノテーションを使うことで、開発者に対してFileDescriptorがSendableとして使用されるべきでないことを明示的に示します。
このような制約が必要な理由としては、FileDescriptorがスレッドセーフでない、もしくはその設計上スレッド間で安全に共有することが適切でない場合が考えられます。これにより、誤った使用方法によるバグや予期しない動作を防ぐことができます。
同時実行ドメイン
複数の非同期タスクを効率的に実行し、データ競合を避けるための機構
同時実行ドメインは、Swiftの非同期プログラミングモデルである「Swift Concurrency」の一部として導入
Swiftの同時実行ドメインの主要な要素には以下のようなものがある
- Actor
- Structed Concurrency
- Task
- Task Group
データ競合やデッドロックを避けながら、複数のタスクを効率的に並行実行することが可能
セマンティック要件
セマンティックの意味わからなかったので、ChatGPTに聞いた
この文での「semantic requirements(セマンティック要件)」は、Swift言語におけるSendableプロトコルに関連する意味的な要件を指します。具体的には、以下のような意味があります。
意味的な制約: セマンティック要件とは、コード自体に明示的には記述されていないが、言語のルールや使用上の慣習として守るべき意味的な制約を指します。Sendableプロトコルの場合、これは例えばスレッドセーフであることや、データの整合性が保たれることを意味します。
コンパイラによる検証: Swiftコンパイラは、Sendableプロトコルに準拠する型がこれらのセマンティック要件を満たしていることを検証します。具体的には、ある型がスレッド間で安全に送信可能であることを保証します。
コード要件の不在: この文脈での「コード要件がない」とは、Sendableプロトコルに具体的なメソッドやプロパティの実装が求められていないことを意味します。その代わりに、型がスレッドセーフであることを意味的に保証する必要があります。
まとめると、この文におけるセマンティック要件は、「Sendableプロトコルに準拠する型が、スレッドセーフであるなどの意味的な制約を満たしていること」を指しています。これにより、Swiftコンパイラは、適切に送信可能な型をチェックし、エラーを防ぐことができます。
Swift6でどう変わるのか知る
以下を読んでみる
チェック内容
Minimal: Swift Concurrencyの利用箇所で、明示的にSendableと書いている箇所でSendable制約とアクター隔離のチェックをします。
Targeted: Swift Concurrencyの利用箇所で、Minimalに加え、明示的にSendableと書いていない箇所でもSendable制約とアクター隔離のチェックをします。
Complete: Swift Concurrencyの利用に関係なく、モジュール全体を通してSendable制約とアクター隔離のチェックをします。
1. アクター隔離されたコンテキストにおいてアクター境界を超えてデータを取得する場合、取得データの型はSendableに準拠する必要がある
以下の例については、ViewModelのviewDidLoad()には@MainActorが付与されているため、viewDidLoad()で取得するデータはSendableを準拠する必要がある
詳しいコードは省略
・Structは暗黙的にはSendableに準拠するが、Publicがつくと明示的に宣言する必要がある
→publicにするということは、その型を外部のモジュールやライブラリでも使用できることを意味します。暗黙的にSendableに準拠させると、開発者がその型が並列性に関して安全であるかどうかを意識せずに公開してしまう可能性があります。
解決策
・@Sendableを付与する
・@preconcurrencyを付与する
→編集ができない場合は、警告を出さないようにできる
@Sendableが付与されたクロージャーのキャプチャ対象の型はSendableに準拠する必要がある
Task{}内で使用するselfなども対象であるが、以下のコードは@MainActorを引き継いでおり、スレッドセーフであることが約束されているので、警告が表示されない
@MainActor func viewDidLoad() {
Task {
staffMember = await self.apiClient.getStaffMember()
}
}
以下の場合は、selfがSendableに準拠する必要がある
解決策
・ViewModel側に@MainActorを付与
今後の方針
・Sendableの理解は直結しますな、、
・Actorの仕様と、Sendableの確認を警告が出ているものに関して行なっていく
・どうSendableに準拠させていくかを考えるのは大変そうだな、、早めにやっていこう
・対応する中で、もっと知識つけていこう