Apple公式Swift tutorialから初学者、中級者が学ぶことは多すぎる

# 概要:
EarthquakesAppを見て、筆者が自分なりに理解したことをつらつら書いていく。
間違いは厳しく教えてほしい。
# 筆者のレベル：
- SwiftUI、まだ完全に理解()してない。<br>
- async/awaitなんとなく使ってる。<br>
- CoreDataなんとなく使ってる。<br>
- ポリモーフィズム？perfumeの新曲？<br>
# 対象読者：
- プログラミングの基礎的な知識がある。
（property/class/struct/method/enum/protocol/...）<br>
- 基礎的なSwiftの構文を理解している。<br>
- 複数の画面を遷移するじゃんけんアプリは作れる。<br>
- もう少し複雑なアプリを美しく書きたい。<br>

# 資料：
https://developer.apple.com/tutorials/app-dev-training/adding-the-detail-view

<details>
<summary>
(LICENSE of "EarthquakesApp"　だいたいMIT)
</summary>

>Portions of this software may utilize or include third party software and other copyrighted material.  The licensing terms and disclaimers for such material are contained in the Apple Software, and Your use of such material is governed by their respective terms which are available for your reference in the ACKNOWLEDGMENTS.txt file. The following license applies to the source code and elements of this package, except where otherwise noted.
>
>Copyright © 2021 Apple Inc.
>
>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

</details>

# EarthquakesAppを読み解く理由
CoreDataを非同期で作成・取得・更新・削除（CRUD）するアプリを実装するのに適したアーキテクチャやデザインパターンがわからないため、少し遠回りして学習したいから。

# このアプリのmodelsの構造
QuakesProvider→QuakeClient/Quake（矢印は依存関係）

- Quake: 純粋なmodel
- QuakeClient: APICliant（actor、非同期）
- QuakesProvider: QuakeClientで取得した[Quake]を、保持、変更するObservableObject（MainActor）

# 読解：

### EarthquakesApp
<details>
<summary>
まずは@mainから見ていく。
</summary>

```EarthquakesApp.swift
@main
struct EarthquakesApp: App {
    @StateObject var quakesProvider = QuakesProvider()
    var body: some Scene {
        WindowGroup {
            Quakes()
                .environmentObject(quakesProvider)
        }
    }
}

```

非常にシンプルで美しい。<br>RootViewであるQuakes()にStateObjectのQuakesProvider()をEnvironmentObjectとして渡しているなあ。<br>
Viewも気になるが、今回はこのmodelから見ていこう。
</details>

### QuakesProvider
<details>
<summary>
QuakesProvider
</summary>

#### 役割
- APIから[Quake]を取得し、それをPublished propertyに保持すること。<br>
- 保持している[Quake]から任意の要素を削除すること。<br>
- QuakeLocationを返すこと。<br>

#### 全体
```QuakesProvider.swift
@MainActor
class QuakesProvider: ObservableObject {

    @Published var quakes: [Quake] = []

    private let client: QuakeClient

    func fetchQuakes() async throws {...}

    func deleteQuakes(atOffsets offsets: IndexSet) {...}

    func location(for quake: Quake) async throws -> QuakeLocation {...}

    init(client: QuakeClient = QuakeClient()) {...}
}
```
#### 詳解
```QuakesProvider.swift
@MainActor
class QuakesProvider: ObservableObject {
  ...
```
おっと、まずclassの前に@MainActorとついている。<br>
これはAttributeの一つで、意味としては、このクラスのpropertyやmethodに変化を加える処理は全てメインスレッドから行われることを保証するんだった。非同期の文脈ではコードの1行1行がそのスレッドで実行されるかわからないからデータの競合の恐れがあるんだよな。でも、だったらclassではなくactorとして宣言したらダメなのかな？あとで調べよう。<br>もしかしてObservableObject protocolに準拠するため？
```QuakesProvider.swift
  ...
  @Published var quakes: [Quake] = []

  private let client: QuakeClient
  ...
```
次に、まずQuakeエンティティの配列が宣言されているな。
そうか、アプリ全体にQuakeを提供するクラスだからQuakesProviderなんだな。<br>このアプリの構成では、<br>App→QuakesProvider→QuakeClient/Quakes（矢印は依存関係）<br>となっているな。<br>

```QuakesProvider.swift
  ...
  func fetchQuakes() async throws {
      let latestQuakes = try await client.quakes
      self.quakes = latestQuakes
  }

  func deleteQuakes(atOffsets offsets: IndexSet) {
      quakes.remove(atOffsets: offsets)
  }

  func location(for quake: Quake) async throws -> QuakeLocation {
      return try await client.quakeLocation(from: quake.detail)
  }

  init(client: QuakeClient = QuakeClient()) {
      self.client = client
  }
}
```
fetchQuakes()はclientからQuakesを取得し取得し、更新しているなあ。<br>じゃあQuakeClientは何らかのAPIとの窓口役なのかな？<br>
deleteQuakes()はその名の通り指定された[Quake]の要素を削除する。<br>location()はどうやら受け取ったQuakeをもってclientに問い合わせ、震源地の情報を返すんだな。init()は初期化関数、デフォルト引数を渡しているから、普段インスタンス化する際は呼ぶ必要は無いけれど、テストの際に値を与えるとき便利そうだ。<br>

</details>

### Quake
<details>
<summary>
Quake
</summary>

#### 役割
- まさにモデルそのもの。アプリの骨格となるデータの設計図。

#### 全体

```Quake.swift
struct Quake {...}

extension Quake: Identifiable {...}

extension Quake: Decodable {...}
```

#### 詳解

```Quake.swift
struct Quake {
    let magnitude: Double
    let place: String
    let time: Date
    let code: String
    let detail: URL
    var location: QuakeLocation?
}
```
まず、基本的な構造は規模、場所（？）、時間、コード（？）、詳細、震源地とシンプルな作りだ。

```Quake.swift
extension Quake: Identifiable {
    var id: String { code }
}
```
ここはIdentifiable protocolに準拠するためにid propetryを追加しているな。<br>
ViewでForEachの中で回すとき、よくIdentifiableに準拠するよね。

```Quake.swift
extension Quake: Decodable {
    private enum CodingKeys: String, CodingKey {
        case magnitude = "mag"
        case place
        case time
        case code
        case detail
    }

    ...
}
```
ここはDecodable protocolに準拠している。<br>
こういうのはJSONからObjectを生成するときに使うんだよな。

</details>

### QuakeClient
<details>
<summary>
QuakeClient
</summary>

#### 役割
- このactorの目的は複数のAPIから地震データを非同期で取得し保持すること<br>
- そのためにURL, decoder, downloader, Casheを持っている<br>
- quakesは非同期のcomputed propertyで地震データをAPIから取得（非同期）<br>
- そのうち条件に適合する複数の地震の震源地をquakeLocation()で同時並行で取得（非同期）

#### 全体

```QuakeClient.swift
actor QuakeClient {//1
    private let quakeCache: NSCache<NSString, CacheEntryObject> = NSCache()//2
    private let feedURL = URL(string: "...")!
    private lazy var decoder: JSONDecoder = {...}()

    private let downloader: any HTTPDataDownloader//3

    var quakes: [Quake] {...}//4

    init(...) {...}

    func quakeLocation(from url: URL) async throws -> QuakeLocation { ...  }//5
}

```

#### 詳解

#####  1
```QuakeClient.swift
actor QuakeClient {
```

まず、QuakeClientはclassではなくactorだ。<br>確かに、ここは非同期的な処理をするはずだから単一スレッドからの処理を保証するとありがたいことが多そう。<br>

#####  2
```QuakeClient.swift
private let quakeCache: NSCache<NSString, CacheEntryObject> = NSCache()
```
NSCacheは作るのにコストがかかるオブジェクトを一時的に保管する場所。
メモリが足りなくなったら破棄される。
キャッシュは辞書っぽく取り出せるらしい。
```CoreFoundation.swift
NSCache<KeyType, ObjectType> : NSObject where KeyType : AnyObject, ObjectType : AnyObject {...}
```
ここではkeyはNSString型でvalueはCacheEntryObject型であることをジェネリクスに渡して宣言しているんだな。
参考：
https://developer.apple.com/documentation/foundation/nscache
https://llcc.hatenablog.com/entry/2015/08/26/234516

feedURL, decoderは省略。<br>
#####  3
```QuakeClient.swift
private let downloader: any HTTPDataDownloader
```
downloaderは「HTTPDataDownloader protocol型」と宣言をしているんだと思う。
```swift
var hoge: any Protocol 
```
で表されるExistential Typeについては<br>https://heart-of-swift.github.io/protocol-oriented-programming/

#####  4
```QuakeClient.swift
var quakes: [Quake] {
        get async throws {
            let data = try await downloader.httpData(from: feedURL)
            let allQuakes = try decoder.decode(GeoJSON.self, from: data)
            var updatedQuakes = allQuakes.quakes
            if let olderThanOneHour = ...//この次に解説
            return updatedQuakes
        }
    }
```
QuakesProvider classにquakes: [Quake]があるのにQuakeClient actorにも同様のプロパティがあるのが気になった。
ただし、こちらはcomputed propertyだし、しかも非同期だ。
内部ではdownloaderで取得したdataをdecoderで[Quake]型オブジェクトにし、直近1時間の地震をolderThanOneHourに代入している。
```QuakeClient.swift
if let olderThanOneHour = updatedQuakes.firstIndex(where: {  $0.time.timeIntervalSinceNow > 3600 }) {
                let indexRange = updatedQuakes.startIndex..<olderThanOneHour
                try await withThrowingTaskGroup(of: (Int, QuakeLocation).self) { group in
                    for index in indexRange {
                        group.addTask {
                            let location = try await self.quakeLocation(from: allQuakes.quakes[index].detail)
                            return (index, location)
                        }
                    }
                    while let result = await group.nextResult() {
                        switch result {
                        case .failure(let error):
                            throw error
                        case .success(let (index, location)):
                            updatedQuakes[index].location = location
                        }
                    }
                }
            }
```
```QuakeClient.swift
if let olderThanOneHour = updatedQuakes.firstIndex(where: {  $0.time.timeIntervalSinceNow > 3600 }) {
                let indexRange = updatedQuakes.startIndex..<olderThanOneHour
```
ここは、updatedQuakesの並び順は[新しい地震,...,古い地震]なんだろうな。
だからindexRangeはstartIndex<1時間を超過するだけ古い地震のうち直近の地震のindex。

```QuakeClient.swift
try await withThrowingTaskGroup(of: (Int, QuakeLocation).self) { group in
...
```
Task Groups は子タスクを並列に実行したいときに使う<br>
子タスクはgroup.addTask {...}で作成し、そのタスクの返し値はawait group.nextResult()で非同期的に受け取り処理をする。
while let ... = await ... はいつまでもnextResultを待ち続けるため、returnしない。
ここでは直近1時間の全ての地震に関して、震源地を非同期関数から受け取っている。<br>
TaskGroupの詳細は：https://qiita.com/maiyama18/items/a1aed9501b8ebfb94d5b

#####  5

```QuakeClient.swift
func quakeLocation(from url: URL) async throws -> QuakeLocation {
    if let cached = quakeCache[url] {   //A
        switch cached {
        case .ready(let location):
            return location
        case .inProgress(let task):
            return try await task.value
        }
      }
    let task = Task<QuakeLocation, Error> {   //B
        let data = try await downloader.httpData(from: url)
        let location = try decoder.decode(QuakeLocation.self, from: data)
        return location
      }
    quakeCache[url] = .inProgress(task)
    do {
        let location = try await task.value
        quakeCache[url] = .ready(location)
        return location
    } catch {
        quakeCache[url] = nil
        throw error
    }
}
```
quakeLocation()でquakeCacheを使っているんだな。


//A<br>
もしもquakeCacheに、<br>
locationがあればそれを返す。<br>
取得taskがあればtask完了を待ち取得したlocationを返す。<br>

//B<br>
取得taskがなければtaskを作成しquakeCacheに代入。<br>
そのtaskの完了を待ち取得したlocationを返す。<br>

</details>

### 気になった文法
<details>
<summary>
enumのassociated value
</summary>



```QuakeClientのquakesのget{}内部.swift
while let result = await group.nextResult() {
switch result {
  case .failure(let error):
     throw error
  case .success(let (index, location)):
     updatedQuakes[index].location = location
  }
}
```
ここのcaseの文法がわからないから調べてみる。<br>まずgroup.nextResult()は<br>Optional\<Result\<ChildTaskResult, Failure>><br>だからoptional　bindingでアンラップしている。
次に
Result\<ChildTaskResult, Failure>
は、上の方で、
```swift
withThrowingTaskGroup<ChildTaskResult, GroupResult>(
                of childTaskResultType: ChildTaskResult.Type,
                ...
                ) async throws -> GroupResult)
```
に対して
```QuakeClientのquakesのget{}内部.swift
try await withThrowingTaskGroup(of: (Int, QuakeLocation).self) { group in
```
のように渡しているから、ここでのChildTaskResultは<br>(Int, QuakeLocation)<br>というタプルになる。<br>即ちresultは<br>Result\<(Int, QuakeLocation), Failure><br>次にResultラッパーについてみてみると<br>

```swift
@frozen public enum Result<Success, Failure> where Failure : Error {

    /// A success, storing a `Success` value.
    case success(Success)

    /// A failure, storing a `Failure` value.
    case failure(Failure)
    ...
```
enumはこの様に場合ごとにvalueを保持できるのか。<br>付属型enum(Associated Value)というらしい。rawValueしか知らなかった。<br>
https://www.swiftlangjp.com/language-guide/enumerations.html#関連値associated-values
https://qiita.com/hachinobu/items/392c96820588d1c03b0c
https://software.small-desk.com/development/2021/03/02/swift-how-to-access-associated-value-in-enum/

なるほど、Associated Valueを持つenumをswitchするときは
```swift
case .value(let associated valueを受け取る変数)

```
とできるんだな。

</details>

# 感想：
これを参考にできるかも？これってアーキテクチャ的にいったら何になるんだろう。
よくわかんないけど、SwiftUI完全に理解した👍

追記：某社の勉強会に参加した際、これがなんのアーキテクチャか伺うと、「アーキテクチャを考えて作っていないんじゃない〜？」的なご回答をいただきました。
アーキテクチャはあくまでも思考や設計過程を円滑で効率的にする道具にすぎないので、そればかりに囚われていてもダメなんですね👍
一方で設計構造のエッセンスを模倣して自分のものにすることで実力は上がると思います。