見出し画像

【じっくりSw1ftUI43】実践編13〜第25章 Swift Actorの紹介

さてと、前回

同時実行の概要

については説明したので〜〜〜今回は、

Actor

をやってく🕺

毎度、オイラの学びなんていらないって人は、

でサンプルコードなんかは載ってるみたいなんで、そっち見てやればいいんじゃね👀💦
さてと、では早速


結論先出し:最後のまとめでやっと書いてるんだけど、Actorと@MainActorって要は、

Actor

  • 非同期コードを作成する際の重要な部分は、データ競合を回避。

  • 2 つ以上のタスクが同じデータにアクセスし、それらのタスクのうちの少なくとも 1 つが書き込み操作を実行するとデータ競合が発生し、同時タスクが同じデータの異なるバージョンを参照して操作する場合、データの不整合が発生する可能性がある。

そのデータ競合を回避するための便利なツール=Actor

Actorは構文的にも動作的にもClassに似ているが、

カプセル化するデータがアプリ内の残りのコードから分離されている

という点が異なる。

インスタンス データを変更するアクター内のメソッドが呼び出された場合、そのメソッドはコード内の他の場所から呼び出す前に完了するまで実行される。

👉複数のタスクが同時にデータを変更しようとすることが防止される。

アクターのメソッド呼び出しとプロパティ アクセスは、

await キーワードを使用

して呼び出す必要がある。

@ MainActor

非同期コード内からメイン スレッドへのアクセスを提供する特別なアクターで、 @ MainActor 属性を使用すると、

型、メソッド、インスタンス、関数、クロージャをマークし、
関連するタスクをメイン スレッドで実行する必要があることを示せる。

最初から動かしながら読む前に

ここは最初に書いとけよ、わかりにき〜〜〜😤

って思ったので、最初に書いた。

  • Actorの必要性

  • Actorとクラスの違い

  • Actorと@MainActor

ってのをサラッと頭に入れてから〜〜〜

じっくり第25章を読んでく👓

第1〜2節くらいで色々書いてるけど、

ここは抜粋した方が理解が進みそうなので〜〜〜

  • アクターは、一度に 1 つのタスクのみがデータにアクセスできるように、内部の可変状態への非同期アクセスを制御する Swift タイプ。

  • アクターは参照型であり、プロパティ、初期化子、およびメソッドを含むという点でクラスに類似。

  • クラスと同様に、アクターもプロトコルに準拠し、拡張機能を使用して拡張できる。

  • アクターを宣言する際の主な違いは、「クラス」の代わりに「アクター」という単語が使用されること。

👉ま、要は、classで作っていたものをActorってので使えるってことね👀

早速コードを組んでみよう

まずは、

class Essentials25Message {
    var adMessage: String = ""
    let contents = "美味しい"
    
    func setStoreName(storeName: String){
        self.adMessage = "\(contents)\(storeName)"
    }
}

てな感じで、クラスをセットして、classをactorに変更

actor Essentials25Message {
    var adMessage: String = ""
    let contents = "美味しい"
    
    func setStoreName(storeName: String){
        self.adMessage = "\(contents)\(storeName)"
    }
}

で、上のコードをどこに組めばいいかがわからないって人もいるだろうから、全体像を示すと、

てな感じで、新しく今回ように作ったSwiftUI用のファイルの中にオイラは組んだ感じ

ただ書き換えてるだけなんだけど、

じゃclassとactorの違いは?👀🧐

重要そうだから、ここも抜粋

  1. classとアクターの主な違いは、アクターは、非同期関数やタスククロージャー内などの非同期コンテキスト内からのみ作成およびアクセスできる。

  2. アクター メソッドを呼び出すとき、またはプロパティにアクセスするときは、awaitキーワードを使用する必要あり。

ま、ここも実際、前回のノリでコードを打ち込んだ方が早そうなんで

とここで、章の全体を見渡したんだけど、、、

分かってる前提なのかあくまでもActorの紹介を重視してるかわからないけど、サンプルコードが、

Playgroundで動かすようなコードにしか見えない👀💦
(この突き放し感、嫌いじゃない藁🤣)

ので、実践編=Xcodeで動くようにオイラで整理🕺
前回まででやった同時実行のコードを参考に〜〜〜

import SwiftUI

actor Essentials25Message {
    var adMessage: String = ""
    let contents = "美味しい"
    
    func setStoreName(storeName: String){
        self.adMessage = "\(contents)\(storeName)"
    }
}

struct Essentials25: View {
    let adMessage = Essentials25Message()

    var body: some View {
        Button(action: {
            Task{
                await actSome()
            }
        }){
            Text("Actorの基本")
        }
        .font(.largeTitle)
        .foregroundStyle(Color.white)
        .background(Color.blue)
        
    }
    
    func actSome() async {
        let storeName = Essentials25Message()
        await storeName.setStoreName(storeName: "🍊果物屋🍎")
        let message = await storeName.adMessage
        print(message)
    }
}

#Preview {
    Essentials25()
}

てな感じのコードにして〜〜〜

ボタンを2回タップすると〜〜〜
てな感じで出力されたね👀

Actorはデータを分離させてる=独立みたい

なので、変更可能なデータにしたい時は、ちゃんと明示させないといけないみたいだね👀

import SwiftUI

actor Essentials25Message {
    var adMessage: String = ""
    let contents = "美味しい"
    
    func setStoreName(storeName: String){
        self.adMessage = "\(contents)\(storeName)"
    }
    nonisolated func getContents() -> String{
        return contents
    }
}

struct Essentials25: View {
    let adMessage = Essentials25Message()
    var adContents = Essentials25Message()

    var body: some View {
        VStack{
            Button(action: {
                Task{
                    await actSome()
                }
            }){
                Text("Actorの基本")
            }
            .font(.largeTitle)
            .foregroundStyle(Color.white)
            .background(Color.blue)
            .padding()
            Button(action: {
                Task{
                    await actAsync()
                }
            }){
                Text("Actor非同期")
            }
            .font(.largeTitle)
            .foregroundStyle(Color.white)
            .background(Color.orange)
            .padding()
            Button(action: {
                Task{
                    actSync()
                }
            }){
                Text("Actor同期")
            }
            .font(.largeTitle)
            .foregroundStyle(Color.white)
            .background(Color.green)
            .padding()
            
        }
    }
    
    func actSome() async {
        let storeName = Essentials25Message()
        await storeName.setStoreName(storeName: "🍊果物屋🍎")
        let message = await storeName.adMessage
        print(message)
    }
    
    func actAsync() async {
        let adContents = adContents.getContents()
        print(adContents)
    }
    
    func actSync() {
        let adContents = adContents.getContents()
        print(adContents)
    }
}

#Preview {
    Essentials25()
}

てな感じで

    nonisolated func getContents() -> String{
        return contents
    }

を入れてあげると、

オレンジと緑のボタンをタップした時に
てな感じの処理結果にはなるみたいなんだけど、、、
    func getContentsErrors() -> String{
        return contents
    }

てな感じで、キーワードを外して

    func actAsyncError() async {
        let adContents =  adContents.getContentsErrors()
        print(adContents)
    }
    
    func actSyncError() {
        let adContents =  adContents.getContentsErrors()
        print(adContents)
    }

みたいなコードをビューの中に追加しても〜〜〜

てな感じでエラーになっちゃってんね👀💦

上のエラーと下のエラーで

上:赤い警告エラー

非同時実行なのにawaitがないって言ってんね👀
Fixをクリックすると
awaitが入ってエラー解消

下は、赤いバツなので

下:Fatal(致命的なエラー)

てゆーていて実際、

nonisolatedキーワードがないと、データが独立のままだからエラーだぞ

つまり、

import SwiftUI

actor Essentials25Message {
    var adMessage: String = ""
    let contents = "美味しい"
    
    func setStoreName(storeName: String){
        self.adMessage = "\(contents)\(storeName)"
    }
    nonisolated func getContents() -> String{
        return contents
    }
    func getContentsAsyncErrors() -> String{
        return contents
    }
    nonisolated func getContentsSyncErrors() -> String{
        return contents
    }
}

struct Essentials25: View {
    let adMessage = Essentials25Message()
    var adContents = Essentials25Message()
    var adContentsAsync = Essentials25Message()
    var adContentsSync = Essentials25Message()
    
    var body: some View {
        VStack{
            Button(action: {
                Task{
                    await actSome()
                }
            }){
                Text("Actorの基本")
            }
            .font(.largeTitle)
            .foregroundStyle(Color.white)
            .background(Color.blue)
            .padding()
            Button(action: {
                Task{
                    await actAsync()
                }
            }){
                Text("Actor非同期")
            }
            .font(.largeTitle)
            .foregroundStyle(Color.white)
            .background(Color.orange)
            .padding()
            Button(action: {
                Task{
                    actSync()
                }
            }){
                Text("Actor同期")
            }
            .font(.largeTitle)
            .foregroundStyle(Color.white)
            .background(Color.green)
            .padding()
            
        }
    }
    
    func actSome() async {
        let storeName = Essentials25Message()
        await storeName.setStoreName(storeName: "🍊果物屋🍎")
        let message = await storeName.adMessage
        print(message)
    }
    
    func actAsync() async {
        let adContents = adContents.getContents()
        print(adContents)
    }
    
    func actSync() {
        let adContents = adContents.getContents()
        print(adContents)
    }
    
    func actAsyncError() async {
        let adContents =  await adContents.getContentsAsyncErrors()
        print(adContents)
    }
    
    func actSyncError() {
        let adContents =  adContents.getContentsSyncErrors()
        print(adContents)
    }
}

#Preview {
    Essentials25()
}

みたいな感じにしてあげないとダメ🙅
ってことね👀💦で実際に、ボタンを追加してみて

てな感じでやってあげると
てな感じで動いたね👀💦

ここは結構、今後、同期処理か非同期処理かActorを使うか否かで

不要なエラーとか同期失敗

なんかで詰まりそうなところかなって思うからしっかり動かしながら、

エラーの原因と対処方法をやっておいた方が良さ気
👉今の例だと簡単に思うかもしれないけど、
特に頭だけでコードを組んで複雑なことをやることを生き甲斐にしてる
どこの現場でもいそうな知識バカほどハマりそう

(オイラならこんな小難しいものはそもそも
iOSアプリのコンセプト=Human Interface Guidleines

に反するから、シンプルにしかやらないけどね〜〜〜〜)

独立データだって明示するのに、

actor Essentials25Message {
    var adMessage: String = ""
    nonisolated let contents = "美味しい"
    
    func setStoreName(storeName: String){
        self.adMessage = "\(contents)\(storeName)"
    }
    nonisolated func getContents() -> String{
        return contents
    }
    func getContentsAsyncErrors() -> String{
        return contents
    }
    nonisolated func getContentsSyncErrors() -> String{
        return contents
    }
}

てな感じで、キーワードを引数の頭につけることもできるみたいで、実行結果は、

てな感じで変わらない👀💦

って言いたいみたい。オイラなら、

第三者がパッと見て安全に改修しやすいように、
必ずキーワードを付ける

ようにするかな💦

べき論と職人気取りの小難しいコードしか書けない自称、プロ(ただの素人)ほど、目先で面倒くさがってこーゆー気配りをせず、

自分でわかりにくくしてる(3日後の自分は他人を知らない)
👉自分で読みにくいコードにして、後から自分の首を絞めるタイプ

な人は、必ずこーゆーところは端折ってるから気をつけてね〜〜〜
*RPA=ロボットの記事ではあるけど

で書いたタイトルが良い例。

動けばいいで何でも自由にやっちゃうと、

保守性とか安全性が低い=機能やデザインが悪い
👉品質の悪い

ものしかできなくて、結果的に

Apple Review Guidlines

に違反しまくったようなアプリしか提出できなくて、

アップルからリジェクト

されてるだけなのに、

  • Appleがおかしい

  • 自分たちの独占状態を維持するのに、不当な扱いを受けた

みたいなことをゆーてるような企業も多いしね。
(日本だとそれを国を挙げて不当に圧力をかけてるけどね👀💦)
自分たちの金儲けのためだけに、

  • ユーザーさんが直観的に扱えない=操作性の悪いデザイン

  • 最低限の機能しかない

  • 個人情報とか児童性愛や犯罪を助長するようなサイトにリンクする

  • 保守性がない

  • 単なる広告目的

みたいなアプリを誰が使いたいと思うのか?
わざわざそれをAppStoreに出す必要があるか?

だけなんだけどね👀💦

そんなアプリばかり出す企業が50年後も生き残ってると思いますか?

ってだけな話。

さてと、本題に戻って、タイムスタンプで遊んでみよう

    func actLong() async -> Date{
        sleep(3)
        return Date()
    }
    
    func actTime() async{
        var buyTimeStamps: [Int: Date] = [:]
        await withTaskGroup(of: Void.self){
            group in for roopCnt in 1 ... 5{
                group.addTask {
                    buyTimeStamps[roopCnt] = await actLong()
                }
            }
        }
    }

コイツを追加してみると、、、

てな感じのエラーが出てんね👀💦

Mutation of captured var 'buyTimeStamps' in concurrently-executing code

をグーグル先生に聞くと、、、

同時実行コード内のキャプチャされた変数「buyTimeStamps」の変異

って何じゃそら?👀💦
(てか、いきなり非同時にしろ同時にしろ、何のActorもなしにこんな関数を組んでるからエラーが起こるんじゃないの?🧐)
って感じで、読み進めると、
まずは

actor buyTimeStore {
    var buyTimeStamp: [Int: Date] = [:]
    func addTimeStamp(menu: Int,date: Date){
        buyTimeStamp[menu] = date
    }
}

を追加して、コード全体としては

import SwiftUI

actor Essentials25Message {
    var adMessage: String = ""
    nonisolated let contents = "美味しい"
    
    func setStoreName(storeName: String){
        self.adMessage = "\(contents)\(storeName)"
    }
    nonisolated func getContents() -> String{
        return contents
    }
    func getContentsAsyncErrors() -> String{
        return contents
    }
    nonisolated func getContentsSyncErrors() -> String{
        return contents
    }
}

actor buyTimeStore {
    var buyTimeStamp: [Int: Date] = [:]
    func addBuyTimeStamp(menu: Int,date: Date){
        buyTimeStamp[menu] = date
    }
}

struct Essentials25: View {
    let adMessage = Essentials25Message()
    var adContents = Essentials25Message()
    var adContentsAsync = Essentials25Message()
    var adContentsSync = Essentials25Message()
    
    var body: some View {
        ScrollView{
            VStack{
                Button(action: {
                    Task{
                        await actSome()
                    }
                }){
                    Text("Actorの基本")
                }
                .font(.largeTitle)
                .foregroundStyle(Color.white)
                .background(Color.blue)
                .padding()
                HStack{
                    Button(action: {
                        Task{
                            await actAsync()
                        }
                    }){
                        Text("Actor非同期")
                    }
                    .font(.largeTitle)
                    .foregroundStyle(Color.white)
                    .background(Color.orange)
                    .padding()
                    
                    Button(action: {
                        Task{
                            actSync()
                        }
                    }){
                        Text("Actor同期")
                    }
                    .font(.largeTitle)
                    .foregroundStyle(Color.white)
                    .background(Color.green)
                    .padding()
                }
                Button(action: {
                    Task{
                        await actAsyncError()
                    }
                }){
                    Text("Actor非同期エラー解消")
                }
                .font(.largeTitle)
                .foregroundStyle(Color.white)
                .background(Color.pink)
                .padding()
                Button(action: {
                    Task{
                        actSyncError()
                    }
                }){
                    Text("Actor同期エラー解消")
                }
                .font(.largeTitle)
                .foregroundStyle(Color.white)
                .background(Color.brown)
                .padding()
                Button(action: {
                    Task{
                        await actTime()
                    }
                }){
                    Text("Actorタイムスタンプ")
                }
                .font(.largeTitle)
                .foregroundStyle(Color.white)
                .background(Color.cyan)
                .padding()
            }
        }
    }
    
    func actSome() async {
        let storeName = Essentials25Message()
        await storeName.setStoreName(storeName: "🍊果物屋🍎")
        let message = await storeName.adMessage
        print(message)
    }
    
    func actAsync() async {
        let adContents = adContents.getContents()
        print(adContents)
    }
    
    func actSync() {
        let adContents = adContents.getContents()
        print(adContents)
    }
    
    func actAsyncError() async {
        let adContents =  await adContents.getContentsAsyncErrors()
        print(adContents)
    }
    
    func actSyncError() {
        let adContents =  adContents.getContentsSyncErrors()
        print(adContents)
    }
    
    func actLong() async -> Date{
        sleep(3)
        return Date()
    }
    
    func actTime() async{
        let timeStore = buyTimeStore()
        
        await withTaskGroup(of: Void.self){
            group in for roopCnt in 1 ... 5{
                group.addTask {
                    await timeStore.addBuyTimeStamp(menu: roopCnt, date: await actLong())
                }
            }
        }
        for (menu, date) in await timeStore.buyTimeStamp{
            print("品目= \(menu), 日付= \(date)")
        }
    }
}

#Preview {
    Essentials25()
}

てな感じで
(ボタンが増えてきたので、ScrollViewを追加して、短いところはHStackで並べた👀💦)

てな感じで

一番下のボタンをタップすると〜〜〜

てな感じでタイムスタンプが押せた👀💦

@MainActor

ここのメンションの使いどころも、抜粋の方がわかりやすそうなので抜粋

"

  1. これまで見てきたように、Swift はメインスレッドとは別のスレッドでタスクを実行するためのシンプルかつ強力なメカニズムを提供。

  2. UI の更新がメイン スレッドでのみ実行されることも重要。

  3. メイン スレッド以外の別のスレッドで UI 更新を実行すると、アプリが不安定になったり、デバッグが困難になる予測不能な動作が発生したりする可能性がある。

  4. Swift 内では、メインスレッドはメインアクターによって表される。

  5. これは、メイン スレッドでコードを実行する必要がある場合に、プログラム コード全体からアクセスできる「Swift アクターの概要」であるため、グローバル アクターと呼ぶ。

  6. アプリを開発するとき、特にそのコードが何らかの方法で UI を更新する場合、メイン アクターで実行したいコードがある状況が発生することがある。

  7. この状況では、@ MainActor 属性を使用してコードをマークできる。

  8. この属性は、関連付けられた操作をメイン アクターで実行する必要があることを示すために、タイプ、メソッド、インスタンス、関数、およびクロージャで使用できる。

  9. たとえば、メインスレッドでのみ動作するようにクラスを構成できる。

"

👉コード全体からアクセスできるグローバルアクターの@MainActorで、実行したいコードをマークさせて、アプリの動きが不安定になったり、予測不能な動作を回避するために使う

ってことが要は言いたいみたいね👀

マークの仕方は、、、

@MainActor
class E25buyTimeStoreMainActor {
    var buyTimeStamp: [Int: Date] = [:]
    func addBuyTimeStamp(menu: Int,date: Date){
        buyTimeStamp[menu] = date
    }
}

class E25buyTimeStoreKeywordMainActor {
    @MainActor var buyTimeStamp: [Int: Date] = [:]
    func addBuyTimeStamp(menu: Int,date: Date){
        buyTimeStamp[menu] = date
    }
}

って言いたいみたいだけど、後者は

てな感じでエラーになるみたいね👀

警告エラーなんで

Fixをクリックして
class E25buyTimeStoreKeywordMainActor {
    @MainActor var buyTimeStamp: [Int: Date] = [:]
    @MainActor func addBuyTimeStamp(menu: Int,date: Date){
        buyTimeStamp[menu] = date
    }
}

こうしないといけないみたい👀💦

👉関数の上に@MainActor=前者の方がまだ読みやすいし、
何をしてるかわかりやすいよね🧐
(オイラはわざわざ関数の中にキーワードとして@を付けないといけないような状況に遭遇した記憶がない)

で、ビューに

    func actMainActor() async {
        await MainActor.run(){
            print("@MainActorから実行")
        }
    }

てな感じで追加して、コード全体としては

import SwiftUI

actor Essentials25Message {
    var adMessage: String = ""
    nonisolated let contents = "美味しい"
    
    func setStoreName(storeName: String){
        self.adMessage = "\(contents)\(storeName)"
    }
    nonisolated func getContents() -> String{
        return contents
    }
    func getContentsAsyncErrors() -> String{
        return contents
    }
    nonisolated func getContentsSyncErrors() -> String{
        return contents
    }
}

actor buyTimeStore {
    var buyTimeStamp: [Int: Date] = [:]
    func addBuyTimeStamp(menu: Int,date: Date){
        buyTimeStamp[menu] = date
    }
}

@MainActor
class E25buyTimeStoreMainActor {
    var buyTimeStamp: [Int: Date] = [:]
    func addBuyTimeStamp(menu: Int,date: Date){
        buyTimeStamp[menu] = date
    }
}

//二つもメインアクターはいらないのでコメントアウト
//class E25buyTimeStoreKeywordMainActor {
//    @MainActor var buyTimeStamp: [Int: Date] = [:]
//    @MainActor func addBuyTimeStamp(menu: Int,date: Date){
//        buyTimeStamp[menu] = date
//    }
//}

struct Essentials25: View {
    let adMessage = Essentials25Message()
    var adContents = Essentials25Message()
    var adContentsAsync = Essentials25Message()
    var adContentsSync = Essentials25Message()
    
    var body: some View {
        ScrollView{
            VStack{
                Button(action: {
                    Task{
                        await actSome()
                    }
                }){
                    Text("Actorの基本")
                }
                .font(.largeTitle)
                .foregroundStyle(Color.white)
                .background(Color.blue)
                .padding()
                HStack{
                    Button(action: {
                        Task{
                            await actAsync()
                        }
                    }){
                        Text("Actor非同期")
                    }
                    .font(.largeTitle)
                    .foregroundStyle(Color.white)
                    .background(Color.orange)
                    .padding()
                    
                    Button(action: {
                        Task{
                            actSync()
                        }
                    }){
                        Text("Actor同期")
                    }
                    .font(.largeTitle)
                    .foregroundStyle(Color.white)
                    .background(Color.green)
                    .padding()
                }
                Button(action: {
                    Task{
                        await actAsyncError()
                    }
                }){
                    Text("Actor非同期エラー解消")
                }
                .font(.largeTitle)
                .foregroundStyle(Color.white)
                .background(Color.pink)
                .padding()
                Button(action: {
                    Task{
                        actSyncError()
                    }
                }){
                    Text("Actor同期エラー解消")
                }
                .font(.largeTitle)
                .foregroundStyle(Color.white)
                .background(Color.brown)
                .padding()
                Button(action: {
                    Task{
                        await actTime()
                    }
                }){
                    Text("Actorタイムスタンプ")
                }
                .font(.largeTitle)
                .foregroundStyle(Color.white)
                .background(Color.cyan)
                .padding()
                Button(action: {
                    Task{
                        await actMainActor()
                    }
                }){
                    Text("@MainActor")
                }
                .font(.largeTitle)
                .foregroundStyle(Color.white)
                .background(Color.indigo)
                .padding()
            }
        }
    }
    
    func actSome() async {
        let storeName = Essentials25Message()
        await storeName.setStoreName(storeName: "🍊果物屋🍎")
        let message = await storeName.adMessage
        print(message)
    }
    
    func actAsync() async {
        let adContents = adContents.getContents()
        print(adContents)
    }
    
    func actSync() {
        let adContents = adContents.getContents()
        print(adContents)
    }
    
    func actAsyncError() async {
        let adContents =  await adContents.getContentsAsyncErrors()
        print(adContents)
    }
    
    func actSyncError() {
        let adContents =  adContents.getContentsSyncErrors()
        print(adContents)
    }
    
    func actLong() async -> Date{
        sleep(3)
        return Date()
    }
    
    func actTime() async{
        let timeStore = buyTimeStore()
        
        await withTaskGroup(of: Void.self){
            group in for roopCnt in 1 ... 5{
                group.addTask {
                    await timeStore.addBuyTimeStamp(menu: roopCnt, date: await actLong())
                }
            }
        }
        for (menu, date) in await timeStore.buyTimeStamp{
            print("品目= \(menu), 日付= \(date)")
        }
    }
    func actMainActor() async {
        await MainActor.run(){
            print("@MainActorから実行")
        }
    }
}

#Preview {
    Essentials25()
}

てな感じで

一番下のボタンを追加

タップすると、、、

てな感じで実行できたね👀

@MainActorについては、サンプルがうっすいので〜〜〜

なんかを参考にしてもらうと良いかも🕺💦
今回は以上。

Apple公式

今回のコードまとめ(キャメルケースも踏まえて、ちょっと綺麗なコードに直した)

import SwiftUI

actor E25Message {
    var adMessage: String = ""
    nonisolated let contents = "美味しい"
    
    func setStoreName(storeName: String){
        self.adMessage = "\(contents)\(storeName)"
    }
    nonisolated func getContents() -> String{
        return contents
    }
    func getContentsAsyncErrors() -> String{
        return contents
    }
    nonisolated func getContentsSyncErrors() -> String{
        return contents
    }
}

actor E25BuyTimeStore {
    var buyTimeStamp: [Int: Date] = [:]
    func addBuyTimeStamp(menu: Int,date: Date){
        buyTimeStamp[menu] = date
    }
}

@MainActor
class E25BuyTimeStoreMainActor {
    var buyTimeStamp: [Int: Date] = [:]
    func addBuyTimeStamp(menu: Int,date: Date){
        buyTimeStamp[menu] = date
    }
}

//二つもメインアクターはいらないのでコメントアウト
//class E25buyTimeStoreKeywordMainActor {
//    @MainActor var buyTimeStamp: [Int: Date] = [:]
//    @MainActor func addBuyTimeStamp(menu: Int,date: Date){
//        buyTimeStamp[menu] = date
//    }
//}

struct Essentials25: View {
    let adMessage = E25Message()
    var adContents = E25Message()
    var adContentsAsync = E25Message()
    var adContentsSync = E25Message()
    
    var body: some View {
        ScrollView{
            VStack{
                Button(action: {
                    Task{
                        await actSome()
                    }
                }){
                    Text("Actorの基本")
                }
                .font(.largeTitle)
                .foregroundStyle(Color.white)
                .background(Color.blue)
                .padding()
                HStack{
                    Button(action: {
                        Task{
                            await actAsync()
                        }
                    }){
                        Text("Actor非同期")
                    }
                    .font(.largeTitle)
                    .foregroundStyle(Color.white)
                    .background(Color.orange)
                    .padding()
                    
                    Button(action: {
                        Task{
                            actSync()
                        }
                    }){
                        Text("Actor同期")
                    }
                    .font(.largeTitle)
                    .foregroundStyle(Color.white)
                    .background(Color.green)
                    .padding()
                }
                Button(action: {
                    Task{
                        await actAsyncError()
                    }
                }){
                    Text("Actor非同期エラー解消")
                }
                .font(.largeTitle)
                .foregroundStyle(Color.white)
                .background(Color.pink)
                .padding()
                Button(action: {
                    Task{
                        actSyncError()
                    }
                }){
                    Text("Actor同期エラー解消")
                }
                .font(.largeTitle)
                .foregroundStyle(Color.white)
                .background(Color.brown)
                .padding()
                Button(action: {
                    Task{
                        await actTime()
                    }
                }){
                    Text("Actorタイムスタンプ")
                }
                .font(.largeTitle)
                .foregroundStyle(Color.white)
                .background(Color.cyan)
                .padding()
                Button(action: {
                    Task{
                        await actMainActor()
                    }
                }){
                    Text("@MainActor")
                }
                .font(.largeTitle)
                .foregroundStyle(Color.white)
                .background(Color.indigo)
                .padding()
            }
        }
    }
    
    func actSome() async {
        let storeName = E25Message()
        await storeName.setStoreName(storeName: "🍊果物屋🍎")
        let message = await storeName.adMessage
        print(message)
    }
    
    func actAsync() async {
        let adContents = adContents.getContents()
        print(adContents)
    }
    
    func actSync() {
        let adContents = adContents.getContents()
        print(adContents)
    }
    
    func actAsyncError() async {
        let adContents =  await adContents.getContentsAsyncErrors()
        print(adContents)
    }
    
    func actSyncError() {
        let adContents =  adContents.getContentsSyncErrors()
        print(adContents)
    }
    
    func actLong() async -> Date{
        sleep(3)
        return Date()
    }
    
    func actTime() async{
        let timeStore = E25BuyTimeStore()
        
        await withTaskGroup(of: Void.self){
            group in for roopCnt in 1 ... 5{
                group.addTask {
                    await timeStore.addBuyTimeStamp(menu: roopCnt, date: await actLong())
                }
            }
        }
        for (menu, date) in await timeStore.buyTimeStamp{
            print("品目= \(menu), 日付= \(date)")
        }
    }
    func actMainActor() async {
        await MainActor.run(){
            print("@MainActorから実行")
        }
    }
}

#Preview {
    Essentials25()
}

さてと、次回は、

VBAやGoogleAppsScriptなんかでもお馴染みの、

イベント

をやってく

第26章 SwiftUI 同時実行性とライフサイクル イベント修飾子

をじっくり読んでく🕺さすが

エッセンシャルズ=素材集

ってだけあって単純な機能紹介ではなく、

実践的な教え方になってるから油断できないし、面白いねえ〜〜〜
まあ、だからこれを記事のネタ元に選んではいるんだが、、、💦👀

記事公開後

いつもどおり、

でやった操作を〜〜〜

てな
てな
てなの
てな感じ

サンプルコード

◾️Essentials25.swift

import SwiftUI
import WebKit

//タイトル
let essentialsChapter25NavigationTitle = "第25章"
let essentialsChapter25Title = "第25章 Swiftアクターの紹介"
let essentialsChapter25SubTitle = "第1節 Swiftアクターの紹介"

//コード
let codeEssentials25 = """
struct Essentials25ContentsView: View {
    let adMessage = E25Message()
    var adContents = E25Message()
    var adContentsAsync = E25Message()
    var adContentsSync = E25Message()
    
    var body: some View {
        ScrollView{
            VStack{
                Button(action: {
                    Task{
                        await actSome()
                    }
                }){
                    Text("Actorの基本")
                }
                .font(.largeTitle)
                .foregroundStyle(Color.white)
                .background(Color.blue)
                .padding()
                HStack{
                    Button(action: {
                        Task{
                            await actAsync()
                        }
                    }){
                        Text("Actor非同期")
                    }
                    .font(.largeTitle)
                    .foregroundStyle(Color.white)
                    .background(Color.orange)
                    .padding()
                    
                    Button(action: {
                        Task{
                            actSync()
                        }
                    }){
                        Text("Actor同期")
                    }
                    .font(.largeTitle)
                    .foregroundStyle(Color.white)
                    .background(Color.green)
                    .padding()
                }
                Button(action: {
                    Task{
                        await actAsyncError()
                    }
                }){
                    Text("Actor非同期エラー解消")
                }
                .font(.largeTitle)
                .foregroundStyle(Color.white)
                .background(Color.pink)
                .padding()
                Button(action: {
                    Task{
                        actSyncError()
                    }
                }){
                    Text("Actor同期エラー解消")
                }
                .font(.largeTitle)
                .foregroundStyle(Color.white)
                .background(Color.brown)
                .padding()
                Button(action: {
                    Task{
                        await actTime()
                    }
                }){
                    Text("Actorタイムスタンプ")
                }
                .font(.largeTitle)
                .foregroundStyle(Color.white)
                .background(Color.cyan)
                .padding()
                Button(action: {
                    Task{
                        await actMainActor()
                    }
                }){
                    Text("@MainActor")
                }
                .font(.largeTitle)
                .foregroundStyle(Color.white)
                .background(Color.indigo)
                .padding()
            }
        }
    }
    
    func actSome() async {
        let storeName = E25Message()
        await storeName.setStoreName(storeName: "🍊果物屋🍎")
        let message = await storeName.adMessage
        print(message)
    }
    
    func actAsync() async {
        let adContents = adContents.getContents()
        print(adContents)
    }
    
    func actSync() {
        let adContents = adContents.getContents()
        print(adContents)
    }
    
    func actAsyncError() async {
        let adContents =  await adContents.getContentsAsyncErrors()
        print(adContents)
    }
    
    func actSyncError() {
        let adContents =  adContents.getContentsSyncErrors()
        print(adContents)
    }
    
    func actLong() async -> Date{
        sleep(3)
        return Date()
    }
    
    func actTime() async{
        let timeStore = E25BuyTimeStore()
        
        await withTaskGroup(of: Void.self){
            group in for roopCnt in 1 ... 5{
                group.addTask {
                    await timeStore.addBuyTimeStamp(menu: roopCnt, date: await actLong())
                }
            }
        }
        for (menu, date) in await timeStore.buyTimeStamp{
            print("品目= \\(menu), 日付= \\(date)")
        }
    }
    func actMainActor() async {
        await MainActor.run(){
            print("@MainActorから実行")
        }
    }
}

#Preview {
    Essentials25ContentsView()
}
"""

//ポイント
let pointEssentials25 = """
最後のまとめでやっと書いてるんだけど、Actorと@MainActorって要は、
◾️Actor
非同期コードを作成する際の重要な部分は、データ競合を回避。
2つ以上のタスクが同じデータにアクセスし、それらのタスクのうちの少なくとも 1 つが書き込み操作を実行するとデータ競合が発生し、同時タスクが同じデータの異なるバージョンを参照して操作する場合、データの不整合が発生する可能性がある。
そのデータ競合を回避するための便利なツール=Actor
Actorは構文的にも動作的にもClassに似ているが、
カプセル化するデータがアプリ内の残りのコードから分離されている
という点が異なる。
インスタンスデータを変更するアクター内のメソッドが呼び出された場合、そのメソッドはコード内の他の場所から呼び出す前に完了するまで実行される。
👉複数のタスクが同時にデータを変更しようとすることが防止される。
アクターのメソッド呼び出しとプロパティ アクセスは、
await キーワードを使用
して呼び出す必要がある。

◾️@ MainActor
非同期コード内からメインスレッドへのアクセスを提供する特別なアクターで、@MainActor 属性を使用すると、型、メソッド、インスタンス、関数、クロージャをマークし、関連するタスクをメインスレッドで実行する必要があることを示せる。
◾️最初から動かしながら読む前に
ここは最初に書いとけよ、わかりにき〜〜〜😤って思ったので、最初に書いた。
・Actorの必要性
・Actorとクラスの違い
・Actorと@MainActor
ってのをサラッと頭に入れてね〜〜〜
"""
//URL
let urlEssentials25 = "https://note.com/m_kakudo/n/n33edacd7ab2e"

//ビュー管理構造体
struct ListiOSApp17DevelopmentEssentialsCh25: Identifiable {
    var id: Int
    var title: String
    var view: ViewEnumiOSApp17DevelopmentEssentialsCh25
}
//遷移先の画面を格納する列挙型
enum ViewEnumiOSApp17DevelopmentEssentialsCh25{
    case Sec1
}
//各項目に表示するリスト項目
let dataiOSApp17DevelopmentEssentialsCh25: [ListiOSApp17DevelopmentEssentialsCh25] = [
    ListiOSApp17DevelopmentEssentialsCh25(id: 1, title: essentialsChapter25SubTitle, view: .Sec1),
]
struct iOSApp17DevelopmentEssentialsCh25: View {
    var body: some View {
        VStack {
            Divider()
            List (dataiOSApp17DevelopmentEssentialsCh25) { data in
                self.containedViewiOSApp17DevelopmentEssentialsCh25(dataiOSApp17DevelopmentEssentialsCh25: data)
            }
            .edgesIgnoringSafeArea([.bottom])
        }
        .navigationTitle(essentialsChapter25NavigationTitle)
        .navigationBarTitleDisplayMode(.inline)
    }
    //タップ後に遷移先へ遷移させる関数
    func containedViewiOSApp17DevelopmentEssentialsCh25(dataiOSApp17DevelopmentEssentialsCh25: ListiOSApp17DevelopmentEssentialsCh25) -> AnyView {
        switch dataiOSApp17DevelopmentEssentialsCh25.view {
        case .Sec1:
            return AnyView(NavigationLink (destination: Essentials25()) {
                Text(dataiOSApp17DevelopmentEssentialsCh25.title)
            })
        }
    }
}
#Preview {
    iOSApp17DevelopmentEssentialsCh25()
}

struct Essentials25: View {
    var body: some View {
        VStack{
            TabView {
                Essentials25ContentsView()
                    .tabItem {
                        Image(systemName: contentsImageTab)
                        Text(contentsTextTab)
                    }
                Essentials25Code()
                    .tabItem {
                        Image(systemName: codeImageTab)
                        Text(codeTextTab)
                    }
                Essentials25Points()
                    .tabItem {
                        Image(systemName: pointImageTab)
                        Text(pointTextTab)
                    }
                Essentials25WEB()
                    .tabItem {
                        Image(systemName: webImageTab)
                        Text(webTextTab)
                    }
            }
        }
    }
}
#Preview {
    Essentials25()
}

struct Essentials25Code: View {
    var body: some View {
        ScrollView{
            Text(codeEssentials25)
        }
    }
}
#Preview {
    Essentials25Code()
}
struct Essentials25Points: View {
    var body: some View {
        ScrollView{
            Text(pointEssentials25)
        }
    }
}
#Preview {
    Essentials25Points()
}
struct Essentials25WebView: UIViewRepresentable {
    let searchURL: URL
    func makeUIView(context: Context) -> WKWebView {
        let view = WKWebView()
        let request = URLRequest(url: searchURL)
        view.load(request)
        return view
    }
    func updateUIView(_ uiView: WKWebView, context: Context) {
        
    }
}
struct Essentials25WEB: View {
    private var url:URL = URL(string: urlEssentials25)!
    var body: some View {Essentials25WebView(searchURL: url)
    }
}
#Preview {
    Essentials25WEB()
}

actor E25Message {
    var adMessage: String = ""
    nonisolated let contents = "美味しい"
    
    func setStoreName(storeName: String){
        self.adMessage = "\(contents)\(storeName)"
    }
    nonisolated func getContents() -> String{
        return contents
    }
    func getContentsAsyncErrors() -> String{
        return contents
    }
    nonisolated func getContentsSyncErrors() -> String{
        return contents
    }
}

actor E25BuyTimeStore {
    var buyTimeStamp: [Int: Date] = [:]
    func addBuyTimeStamp(menu: Int,date: Date){
        buyTimeStamp[menu] = date
    }
}

@MainActor
class E25BuyTimeStoreMainActor {
    var buyTimeStamp: [Int: Date] = [:]
    func addBuyTimeStamp(menu: Int,date: Date){
        buyTimeStamp[menu] = date
    }
}

//二つもメインアクターはいらないのでコメントアウト
//class E25buyTimeStoreKeywordMainActor {
//    @MainActor var buyTimeStamp: [Int: Date] = [:]
//    @MainActor func addBuyTimeStamp(menu: Int,date: Date){
//        buyTimeStamp[menu] = date
//    }
//}

struct Essentials25ContentsView: View {
    let adMessage = E25Message()
    var adContents = E25Message()
    var adContentsAsync = E25Message()
    var adContentsSync = E25Message()
    
    var body: some View {
        ScrollView{
            VStack{
                Button(action: {
                    Task{
                        await actSome()
                    }
                }){
                    Text("Actorの基本")
                }
                .font(.largeTitle)
                .foregroundStyle(Color.white)
                .background(Color.blue)
                .padding()
                HStack{
                    Button(action: {
                        Task{
                            await actAsync()
                        }
                    }){
                        Text("Actor非同期")
                    }
                    .font(.largeTitle)
                    .foregroundStyle(Color.white)
                    .background(Color.orange)
                    .padding()
                    
                    Button(action: {
                        Task{
                            actSync()
                        }
                    }){
                        Text("Actor同期")
                    }
                    .font(.largeTitle)
                    .foregroundStyle(Color.white)
                    .background(Color.green)
                    .padding()
                }
                Button(action: {
                    Task{
                        await actAsyncError()
                    }
                }){
                    Text("Actor非同期エラー解消")
                }
                .font(.largeTitle)
                .foregroundStyle(Color.white)
                .background(Color.pink)
                .padding()
                Button(action: {
                    Task{
                        actSyncError()
                    }
                }){
                    Text("Actor同期エラー解消")
                }
                .font(.largeTitle)
                .foregroundStyle(Color.white)
                .background(Color.brown)
                .padding()
                Button(action: {
                    Task{
                        await actTime()
                    }
                }){
                    Text("Actorタイムスタンプ")
                }
                .font(.largeTitle)
                .foregroundStyle(Color.white)
                .background(Color.cyan)
                .padding()
                Button(action: {
                    Task{
                        await actMainActor()
                    }
                }){
                    Text("@MainActor")
                }
                .font(.largeTitle)
                .foregroundStyle(Color.white)
                .background(Color.indigo)
                .padding()
            }
        }
    }
    
    func actSome() async {
        let storeName = E25Message()
        await storeName.setStoreName(storeName: "🍊果物屋🍎")
        let message = await storeName.adMessage
        print(message)
    }
    
    func actAsync() async {
        let adContents = adContents.getContents()
        print(adContents)
    }
    
    func actSync() {
        let adContents = adContents.getContents()
        print(adContents)
    }
    
    func actAsyncError() async {
        let adContents =  await adContents.getContentsAsyncErrors()
        print(adContents)
    }
    
    func actSyncError() {
        let adContents =  adContents.getContentsSyncErrors()
        print(adContents)
    }
    
    func actLong() async -> Date{
        sleep(3)
        return Date()
    }
    
    func actTime() async{
        let timeStore = E25BuyTimeStore()
        
        await withTaskGroup(of: Void.self){
            group in for roopCnt in 1 ... 5{
                group.addTask {
                    await timeStore.addBuyTimeStamp(menu: roopCnt, date: await actLong())
                }
            }
        }
        for (menu, date) in await timeStore.buyTimeStamp{
            print("品目= \(menu), 日付= \(date)")
        }
    }
    func actMainActor() async {
        await MainActor.run(){
            print("@MainActorから実行")
        }
    }
}

#Preview {
    Essentials25ContentsView()
}

◾️EssentialsMenu.swift

//フレームワーク
import SwiftUI
import WebKit

//ビュー管理構造体
struct ListiOSApp17DevelopmentEssentials: Identifiable {
    var id: Int
    var title: String
    var view: ViewEnumiOSApp17DevelopmentEssentials
}
//遷移先の画面を格納する列挙型
enum ViewEnumiOSApp17DevelopmentEssentials {
    case Ch1
    //じっくり13で追加
    case Ch2
    //じっくり14で追加
    case Ch3
    //じっくり15で追加
    case Ch4
    //じっくり16で追加
    case Ch5
    //じっくり17で追加
    case Ch6
    //じっくり18で追加
    case Ch7
    //じっくり19で追加
    case Ch8
    //じっくり20、21で追加
    case Ch9
    //じっくり22、23で追加
    case Ch10
    //じっくり24で追加
    case Ch11
    //じっくり25で追加
    case Ch12
    //じっくり26で追加
    case Ch13
    //じっくり27,28で追加
    case Ch14
    //じっくり29で追加
    case Ch15
    //じっくり31で追加
    case Ch16
    //じっくり32で追加
    case Ch17
    //じっくり33で追加
    case Ch18
    //じっくり34で追加
    case Ch19
    //じっくり35で追加
    case Ch20
    //じっくり36で追加
    case Ch21
    //じっくり37で追加
    case Ch22
    //じっくり40で追加
    case Ch23
    //じっくり41で追加
    case Ch24
    //じっくり43で追加
    case Ch25
}
//各項目に表示する文字列
let dataiOSApp17DevelopmentEssentials: [ListiOSApp17DevelopmentEssentials] = [
    ListiOSApp17DevelopmentEssentials(id: 1, title: essentialsChapter1Title, view: .Ch1),
    //じっくり13で追加
    ListiOSApp17DevelopmentEssentials(id: 2, title: essentialsChapter2Title, view: .Ch2),
    //じっくり13で追加
    ListiOSApp17DevelopmentEssentials(id: 3, title: essentialsChapter3Title, view: .Ch3),
    //じっくり15で追加
    ListiOSApp17DevelopmentEssentials(id: 4, title: essentialsChapter4Title, view: .Ch4),
    //じっくり16で追加
    ListiOSApp17DevelopmentEssentials(id: 5, title: essentialsChapter5Title, view: .Ch5),
    //じっくり17で追加
    ListiOSApp17DevelopmentEssentials(id: 6, title: essentialsChapter6Title, view: .Ch6),
    //じっくり18で追加
    ListiOSApp17DevelopmentEssentials(id: 7, title: essentialsChapter7Title, view: .Ch7),
    //じっくり19で追加
    ListiOSApp17DevelopmentEssentials(id: 8, title: essentialsChapter8Title, view: .Ch8),
    //じっくり20、21で追加
    ListiOSApp17DevelopmentEssentials(id: 9, title: essentialsChapter9Title, view: .Ch9),
    //じっくり22、23で追加
    ListiOSApp17DevelopmentEssentials(id: 10, title: essentialsChapter10Title, view: .Ch10),
    //じっくり24で追加
    ListiOSApp17DevelopmentEssentials(id: 11, title: essentialsChapter11Title, view: .Ch11),
    //じっくり25で追加
    ListiOSApp17DevelopmentEssentials(id: 12, title: essentialsChapter12Title, view: .Ch12),
    //じっくり26で追加
    ListiOSApp17DevelopmentEssentials(id: 13, title: essentialsChapter13Title, view: .Ch13),
    //じっくり27,28で追加
    ListiOSApp17DevelopmentEssentials(id: 14, title: essentialsChapter14Title, view: .Ch14),
    //じっくり29で追加
    ListiOSApp17DevelopmentEssentials(id: 15, title: essentialsChapter15Title, view: .Ch15),
    //じっくり31で追加
    ListiOSApp17DevelopmentEssentials(id: 16, title: essentialsChapter16Title, view: .Ch16),
    //じっくり32で追加
    ListiOSApp17DevelopmentEssentials(id: 17, title: essentialsChapter17Title, view: .Ch17),
    //じっくり33で追加
    ListiOSApp17DevelopmentEssentials(id: 18, title: essentialsChapter18Title, view: .Ch18),
    //じっくり34で追加
    ListiOSApp17DevelopmentEssentials(id: 19, title: essentialsChapter19Title, view: .Ch19),
    //じっくり35で追加
    ListiOSApp17DevelopmentEssentials(id: 20, title: essentialsChapter20Title, view: .Ch20),
    //じっくり36で追加
    ListiOSApp17DevelopmentEssentials(id: 21, title: essentialsChapter21Title, view: .Ch21),
    //じっくり37で追加
    ListiOSApp17DevelopmentEssentials(id: 22, title: essentialsChapter22Title, view: .Ch22),
    //じっくり40で追加
    ListiOSApp17DevelopmentEssentials(id: 23, title: essentialsChapter23Title, view: .Ch23),
    //じっくり41で追加
    ListiOSApp17DevelopmentEssentials(id: 24, title: essentialsChapter24Title, view: .Ch24),
    //じっくり43で追加
    ListiOSApp17DevelopmentEssentials(id: 25, title: essentialsChapter25Title, view: .Ch25),
]

struct iOSApp17DevelopmentEssentials: View {
    var body: some View {
        VStack {
            Divider()
            List (dataiOSApp17DevelopmentEssentials) { data in
                self.containedViewiOSApp17DevelopmentEssentials(dataiOSApp17DevelopmentEssentials: data)
            }
            .edgesIgnoringSafeArea([.bottom])
        }
        .navigationTitle("iOS開発の章目次")
        .navigationBarTitleDisplayMode(.inline)
    }
    //タップ後に遷移先へ遷移させる関数
    func containedViewiOSApp17DevelopmentEssentials(dataiOSApp17DevelopmentEssentials: ListiOSApp17DevelopmentEssentials) -> AnyView {
        switch dataiOSApp17DevelopmentEssentials.view {
        case .Ch1:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh1()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり13で追加
        case .Ch2:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh2()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり13で追加
        case .Ch3:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh3()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり15で追加
        case .Ch4:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh4()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり16で追加
        case .Ch5:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh5()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり17で追加
        case .Ch6:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh6()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり18で追加
        case .Ch7:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh7()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり19で追加
        case .Ch8:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh8()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり20、21で追加
        case .Ch9:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh9()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり22、23で追加
        case .Ch10:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh10()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり24で追加
        case .Ch11:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh11()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり25で追加
        case .Ch12:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh12()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり26で追加
        case .Ch13:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh13()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり27,28で追加
        case .Ch14:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh14()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり29で追加
        case .Ch15:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh15()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり31で追加
        case .Ch16:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh16()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり32で追加
        case .Ch17:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh17()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり33で追加
        case .Ch18:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh18()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり34で追加
        case .Ch19:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh19()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり35で追加
        case .Ch20:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh20()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり36で追加
        case .Ch21:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh21()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり37で追加
        case .Ch22:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh22()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり40で追加
        case .Ch23:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh23()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり41で追加
        case .Ch24:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh24()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
            //じっくり43で追加
        case .Ch25:
            return AnyView(NavigationLink (destination: iOSApp17DevelopmentEssentialsCh25()) {
                Text(dataiOSApp17DevelopmentEssentials.title)
            })
        }
    }
}

#Preview {
    iOSApp17DevelopmentEssentials()
}

以上。
さてと、

💃新聞と温泉とノーマンで明日からの仕事に備えよ🕺
福岡市は今日は生憎の雨〜〜〜
みなさんも残り少ないけど、良い休日を〜〜〜


この記事が気に入ったらサポートをしてみませんか?