前節で, リストのデータをJSONファイルに書き出す・読み込む・削除することができるようになりました. またファイルがない場合の初期化処理も取り扱いました. 一方, 昨今のAppではデータをストレージに保存する以外のパターンが多くなりました。つまりネットワークを介してサーバにアクセスし, JSONといったデータを受け取るという方法です. 今回はJSONを返してくれるサーバにデータをリクエストする例を紹介します.

  • SFSwiftUI03-4.swiftpm を開く (サンプルコード)
    • 3-2-5を完了したコードと同様
      • photos配列は空っぽ
      • Resources/photos.json も存在しない
    • また簡単のため,3-3で作成した追加や削除の機能も省いている

コード1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import SwiftUI

struct ContentView: View {
    @State var photos:[PhotoData] = []
    
    var body: some View {
        List {
            Section {
                ForEach( $photos ) { bind_photo in
                    NavigationLink( destination:{
                        SceneryDetail( data:bind_photo )
                    },
                    label: {
                        SceneryRow( data:bind_photo.wrappedValue )
                    })
                }
            }
            header: {
                HStack {
                    Text( "景色写真" )
                    
                    Spacer()
                }
            }
        }
        .listStyle( .grouped )
        .navigationTitle( "写真集" )
    }
}

実行結果

swiftui_3_4_1_1.png

何もない状況なので, リストが空っぽになります. 今回は読み込めるJSONファイルもないのでサーバにデータをリクエストする処理を追加します.

  • APIAccessor.swiftを新規作成し, サーバにアクセスするコードを記述する

swiftui_3_4_1_2.png

swiftui_3_4_1_3.png

  • static func requestPhotos() async関数 を作る
    • 戻り値は**[PhotoData]** とする
    • async : 非同期であること示す
      • サーバアクセスは並列に処理するので非同期にする
    • APIの場所は, https://archive.wdkk.co.jp/sf-swiftui/api/photos/
      • リクエストに成功するとJSONデータが得られる
    • URLRequest( url: ) でアクセスするための情報を用意する
      • URLSession.shared.data( for: ) でサーバにリクエストを送る
  • 例外を扱う try! と, また非同期関数であるので処理完了を待つ await をつける
  • サーバから結果(レスポンス)を得られた場合, result.0 に結果が入っている
  • JSONDecoder().decode( … )result.0 を [PhotoData] に変換する.
  • 変換結果をreturnで返す

コード2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import Foundation

class APIAccessor {
    static func requestPhotos() async -> [PhotoData] {
        let api_photos = "https://archive.wdkk.co.jp/sf-swiftui/api/photos/"
        guard let url = URL( string:api_photos ) else { 
            print( "URLが正しくありません" )
            return []
        }
        
        let request = URLRequest( url:url )
        
        let result = try! await URLSession.shared.data(for: request )
        let json = try! JSONDecoder().decode( [PhotoData].self, from:result.0 )
        return json
    }
}

これで指定したサーバにリクエストする処理ができました. 作成した APIAccessor.requestPhotos() をContentViewで使ってみます. こ注意点はrequestPhotos()は async(非同期)関数 であることです. 非同期ですので他の処理と独立したタスクであることを示す必要があります.

  • ContentView.swiftを開く
  • .onAppear { … } を追加し, 画面表示時にAPIアクセスを試みる (※ネット接続が必須)
  • Task { … } を追加. Task内は独立した非同期処理となる
    • APIAccessor.requestPhotos() を使うとき await をつける
      • サーバへリクエストしつつ, 処理が終わるまで待機する
      • データが得られ次第処理を再開, 結果をphotosへ代入する
  • 実行し, サーバからの結果がリスト化されたら成功

コード3

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import SwiftUI

struct ContentView: View {
    @State var photos:[PhotoData] = []
    
    var body: some View {
        List {
            Section {
                ForEach( $photos ) { bind_photo in
                    NavigationLink( destination:{
                        SceneryDetail( data:bind_photo )
                    },
                    label: {
                        SceneryRow( data:bind_photo.wrappedValue )
                    })
                }
            }
            header: {
                HStack {
                    Text( "景色写真" )
                    
                    Spacer()
                }
            }
        }
        .listStyle( .grouped )
        .navigationTitle( "写真集" )
        .onAppear {
            Task {
                photos = await APIAccessor.requestPhotos()
            }
        }
    }
}

結果

swiftui_3_4_1_4.png

今回App内にはリストに表示されたデータはないはずです. それでもリストが表示されたということは外部からデータをとってくることに成功したためです. サーバを利用した多くのソフトウェアサービスはこのように動作していることがわかります. なお,サーバからかえってきた結果を確認したい場合, .onAppear内で print( photos ) を書き, コンソールに出してみてください.