|
| 1 | +--- |
| 2 | +title: "Swift6移行に向けて、Isolation domainとSendableを理解する" |
| 3 | +date: 2024/12/08 00:00:00 |
| 4 | +postid: a |
| 5 | +tag: |
| 6 | + - iOS |
| 7 | + - Swift |
| 8 | + - Swift6 |
| 9 | +category: |
| 10 | + - Programming |
| 11 | +thumbnail: /images/20241208a/thumbnail.png |
| 12 | +author: 橋本竜我 |
| 13 | +lede: "SwiftZoomin#20の内容から、Swift6移行に向けて理解が必要なSwift Concurrencyの重要な概念について簡単にまとめました。" |
| 14 | +--- |
| 15 | +<img src="/images/20241208a/image.png" alt="" width="1200" height="416" loading="lazy"> |
| 16 | + |
| 17 | +<div class="note info" style="background: #e5f8e2; padding:16px; margin:24px 12px; border-radius:8px;"><span class="fa fa-fw fa-check-circle"></span> |
| 18 | + |
| 19 | +本記事は、[Swift Advent Calendar 2024](https://qiita.com/advent-calendar/2024/swift)の8日目です。 |
| 20 | +7日目は、[@hinakko](https://qiita.com/hinakko)さんの[Split Viewを考慮したSize Classを使用したiPad対応](https://qiita.com/hinakko/items/ac65d149e3141a8bc7da)です。 |
| 21 | + |
| 22 | +</div> |
| 23 | + |
| 24 | +HealthCare Innovation Group(HIG)[^1]の橋本です。 |
| 25 | + |
| 26 | +先日参加したSwiftZoomin#20の内容から、Swift6移行に向けて理解が必要なSwift Concurrencyの重要な概念について簡単にまとめました。 |
| 27 | + |
| 28 | +SwiftZoomin#20の動画は、次のリンク先からYoutube上で視聴可能です。 |
| 29 | +- [感覚的に理解するConcurrency: Swift 6はIsolationとSendableを用いてどのようにデータ競合を防止するか](https://youtu.be/AUcn2y2jjNs?si=_fyNjme2hDA236sl) |
| 30 | + |
| 31 | +# Swift6移行に向けて、重要な概念3つ |
| 32 | + |
| 33 | +以下3つの概念についてまとめいきます。 |
| 34 | + |
| 35 | +- Isolation domain |
| 36 | +- Isolation boundary |
| 37 | +- Sendable |
| 38 | + |
| 39 | +## Isolation domain |
| 40 | + |
| 41 | +`隔離=Isolation`する領域のことを`Isolation domain`と呼びます。 |
| 42 | +`Isolation domain`の重要な特性は、**一つのIsolation domainのなかでは同時に一つの処理しか実行されない**ことです。(=一つのIsolation domainの中で並行実行されることはないことと同義) |
| 43 | + |
| 44 | +参考 |
| 45 | + |
| 46 | +- [Isolation Domains | Migrating to Swift 6](https://www.swift.org/migration/documentation/swift-6-concurrency-migration-guide/dataracesafety/#Isolation-Domains) |
| 47 | + |
| 48 | +### actor |
| 49 | + |
| 50 | +`actor`というのは、一つの領域で隔離(Isolation)することでデータ競合を防いでいます。 |
| 51 | + |
| 52 | +最も馴染み深いのは、`MainActor`の`Isolation domain`であり、`actor`のインスタンスに紐づいたIsolation domainも存在します。 |
| 53 | + |
| 54 | +`MainActor`の`Isolation domain`は`Main Actor`で保護された領域を表します。 |
| 55 | +`Actor`の場合は、`Actor`のインスタンスごとに`Isolation domain`をもち、同じ`actor`でもインスタンスが異なれば、それぞれ異なる`Isolation domain`を持ちます。 |
| 56 | + |
| 57 | +```swift |
| 58 | +actor Counter {...} |
| 59 | + |
| 60 | +let a = Counter() // aのIsolation domain、aとbは別のIsolation domainである。 |
| 61 | +let b = Counter() // bのIsolation domain、aとbは別のIsolation domainである。 |
| 62 | + |
| 63 | +``` |
| 64 | + |
| 65 | +参考 |
| 66 | + |
| 67 | +- [Actor | Apple Developer Documentation](https://developer.apple.com/documentation/swift/actor) |
| 68 | + |
| 69 | +## Isolation boundary |
| 70 | + |
| 71 | +`Isolation domain`が複数存在しているときのそれぞれの`Isolation domain`の間の境界のことを`Isolation boundary`と呼びます。 |
| 72 | + |
| 73 | +アプリにおいては、それぞれの`Isolation domain`間で`Isolation boundary`を超えて情報のやり取りをしないといけない場面が多々あります。 |
| 74 | + |
| 75 | +この`Isolation boundary`を超えて値を受け渡すために、次の`Sendable`が必要になってきます。 |
| 76 | + |
| 77 | +## Sendable |
| 78 | + |
| 79 | +**Sendable 並行にアクセスされても安全な型だけが準拠できるプロトコル** |
| 80 | +`Sendable`の特徴は、`Sendable`に準拠した型の値だけが`Isolation boudnary`を超えられるようになります。 |
| 81 | +つまり、`Sendable`を使うことで実行時にデータ競合が起こらないことをコンパイル時にチェックできるように、型の問題とすることで実現しています。 |
| 82 | + |
| 83 | +### non-Sendable |
| 84 | + |
| 85 | +`Sendable`に対して、`non-Sendable`な値は、`Isolation boundary`を超えることができません。 |
| 86 | + |
| 87 | + |
| 88 | +- [Sendable | Apple Developer Documentation](https://developer.apple.com/documentation/swift/sendable) |
| 89 | +- [Sendable Types | Migrating to Swift 6](https://www.swift.org/migration/documentation/swift-6-concurrency-migration-guide/dataracesafety/#Sendable-Types) |
| 90 | +- [Sendable Types | The Swift Programming Language](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/#Sendable-Types) |
| 91 | + |
| 92 | +## Sendableを体感する |
| 93 | + |
| 94 | +<img src="/images/20241208a/Sendable.png" alt="" width="1031" height="685" loading="lazy"> |
| 95 | + |
| 96 | +上記の`Box`クラスをSendableに準拠させて、並行にアクセス可能にさせてみる。 |
| 97 | + |
| 98 | +```swift |
| 99 | +final class Box: Sendable { |
| 100 | + var value: Int = 0 // ここでエラー(Stored property 'value' of 'Sendable'-conforming class 'Box' is mutable) |
| 101 | +} |
| 102 | +``` |
| 103 | + |
| 104 | +そうすると、次のように`Sendable`に準拠すること自体がエラーであることがわかります。 |
| 105 | +`Box`型のプロパティ`Value`が可変状態のため、並行にアクセスすると危険である旨のメッセージが出てきます。 |
| 106 | + |
| 107 | +```txt |
| 108 | +Stored property 'value' of 'Sendable'-conforming class 'Box' is mutable |
| 109 | +``` |
| 110 | + |
| 111 | +ここで、`var`ではなく、`let`だったらどうなるか考えてみます。 |
| 112 | + |
| 113 | +```swift |
| 114 | +final class Box: Sendable { |
| 115 | + let value: Int = 0 |
| 116 | +} |
| 117 | +``` |
| 118 | + |
| 119 | +`let`にすることで、`Box`クラスのエラーは解消されます。 |
| 120 | + |
| 121 | +`value`プロパティが定数になるため、`run()`メソッド内で`box.balue = -1`で書き込みをしていた箇所がコンパイルエラーになります。 |
| 122 | + |
| 123 | +この部分を読み込むだけの`print(box.value)`とすれば、データ競合を起こさない安全なコードとすることができます。データ競合は、並行にアクセスする少なくとも1つで書き換えが起こっているときに生じるものであるため、今回のように読み込むだけであれば、データ競合の危険性はありません。 |
| 124 | + |
| 125 | +```swift |
| 126 | +func run() { |
| 127 | + let box: Box = .init() |
| 128 | + |
| 129 | + Task { |
| 130 | + // box.value = -1 // Cannot assign to property: 'value' is a 'let' constant |
| 131 | + print(box.value) |
| 132 | + } |
| 133 | + |
| 134 | + Task { |
| 135 | + print(box.value) |
| 136 | + } |
| 137 | +} |
| 138 | +``` |
| 139 | + |
| 140 | +## Isolation boundaryを体感する |
| 141 | + |
| 142 | +`Sendable`に準拠していない`non-Sendable`な`Box`クラスが`Isolation boundary`を超えるとコンパイルエラーが出る例を示します。 |
| 143 | + |
| 144 | +```swift |
| 145 | +final class Box { |
| 146 | + var value: Int = 0 |
| 147 | +} |
| 148 | + |
| 149 | + |
| 150 | +actor A { |
| 151 | + var box: Box? |
| 152 | + func setBox(_ box: Box) { |
| 153 | + self.box = box |
| 154 | + } |
| 155 | +} |
| 156 | + |
| 157 | + |
| 158 | +func run() async { |
| 159 | + let box: Box = .init() |
| 160 | + let a: A = .init() // actor A のインスタンスa |
| 161 | + |
| 162 | + await a.setBox(box) // actor A のインスタンスaにboxを渡す ⇐ boxがIsolation bounadaryを超えて、actor AのインスタンスaのIsolation domainに入る。 |
| 163 | +// ここで次のエラーメッセージが出る。 |
| 164 | +// Sending 'box' risks causing data races |
| 165 | + |
| 166 | + print(box.value) |
| 167 | +} |
| 168 | +``` |
| 169 | + |
| 170 | +`Sendable`に準拠していない`Box`クラスの`box`インスタンスが`Isolaton boudary`を超えたため、このエラーがでてきます。 |
| 171 | + |
| 172 | +このように、`Isolaton boudary`が存在していることを体験することができました。 |
| 173 | + |
| 174 | +# おわりに |
| 175 | + |
| 176 | +SwiftZoomin#20([感覚的に理解するConcurrency: Swift 6はIsolationとSendableを用いてどのようにデータ競合を防止するか](https://youtu.be/AUcn2y2jjNs?si=_fyNjme2hDA236sl))動画前半の内容について私なりにまとめさせていただきました。([@koher](https://qiita.com/koher)さん、大変わかりやすく説明いただきありがとうございました。) |
| 177 | + |
| 178 | +Swift6への移行は、コンパイル時にデータ競合が起こる可能性のある箇所を事前につぶすことができるので、アプリの品質向上に直結するため、ビジネス的にもとても有用なものだと思っております。 |
| 179 | + |
| 180 | +本記事がSwift6移行に向けて、少しでも参考になれば幸いです。 |
| 181 | + |
| 182 | +[^1]: 医療・ヘルスケア分野での案件や新規ビジネス創出を担う、2020年に誕生した事業部です。設立エピソードは[未来報](https://note.future.co.jp/n/n8b57d4bf4604)の記事をご覧ください。 |
0 commit comments