MVVMモードに従ってHTTPリクエストをインポートしたサーバデータをアプリケーションに適用


前回のプレゼンテーションでは、MVVMモードと実際のアプリケーションへの適用方法、およびモデル、ビュー、およびビューモデルがそれぞれどのような役割を果たすかについて説明しました.
そこで今回、実際のプロジェクトでは、HTTPリクエストのGETメソッドでサーバからデータを受信し、MVVMモードに従って画面に表示します.
MVVMモードについて詳しくない場合は、以下の位置を参照してください.
MVVMモードの場合

💻 プリセット


HTTPリクエストではGETメソッドを使用します.
HTTPリクエストをサーバに送信する前に、プロジェクトをいくつか設定しました.
ちなみに、プロジェクト構造については、「Root」はプロジェクトのルートフォルダを表します.

-プロジェクトでのインターネット使用の設定(Root/Info.plist)


これは、アプリケーション内でネットワークを使用するために設定されています.
  • プロジェクトフォルダのInfo.plistに入り、Information Property ListのAddボタン(+)をクリックして、新しい設定項目を作成します.
  • で作成したコンフィギュレーション・アイテムの名前を「App Transport Security Settings」に変更し、そのアイテムに「追加」ボタン(+)をクリックして、そのアイテムのサブアイテムとしてコンフィギュレーション・アイテムを作成します.
  • 2号で作成した設定項目の名前を「Allow ArbitraryLoads」、Typeを「Boolean」、値を「YES」に変更します.

  • 前述したように、プロジェクト、すなわち開発中のアプリケーションでは、インターネットを使用することができる.

    - Root/Utils/Constants.swift


    定数を保存するためのファイルです.
    筆者は通信するサーバのIPアドレスとポート番号を保存した.
    import Foundation
    
    struct Constants {
        static let IP_ADDRESS = "Your_IP_Address"
        static let PORT_NUM = "8080"
    }
    
    後でIPアドレスまたはポート番号が変化した場合、Constants.swift内で定数値を変更するだけです.

    - Root/Extensions/URLExtensions.swift


    筆者は,サーバからゲームデータを受信し,各ゲームの名称をパラメータとして渡し,そのゲームを格納するサーバのURLを返す関数を生成する.
    IPアドレスとポート番号は、上記で作成したConstantsです.swiftリファレンスで使用します.
    import Foundation
    
    extension URL {
        
        static func forRandomGameByName(_ gameName: String) -> URL? {
            return URL(string: "http://\(Constants.IP_ADDRESS):\(Constants.PORT_NUM)/games/\(gameName)/random")
        }
        
        static func forGameByNameAndID(_ gameName: String, _ id: String) -> URL? {
            return URL(string: "http://\(Constants.IP_ADDRESS):\(Constants.PORT_NUM)/games/\(gameName)/\(id)")
        }
        
    }
    
    ゲームデータをランダムに受信するクエリー関連URLと、ゲームIDで特定のゲームデータを受信するクエリー関連URLの2つを含む.

    💻 モデルの作成


    - Root/Models/GameModel.swift


    プリセットが完了すると、M-V-VMモードでモデルが作成されます.
    モデルを作成する場合は、受信するJSONデータをフォーマットするだけです.
    筆者が受け取るデータは以下の通りです.
    {
        "id": 0,
        "solution1": "Blah Blah",
        "solution2": "Blah Blah"
    }
    対応するデータフォーマットに対して、「BanceGame」構造体を「Codable」と宣言します.
    コラボレーション可能と宣言することで、サーバはそれぞれ「ソリューション1」と「ソリューション2」として格納できますが、プロジェクトでは「firstchoice」と「secondchoice」変数名を使用できます.
    私が使っている変数名はStringで、サーバー上の変数名はCodingKeyです.ここで注意したいのは、CodingKeyを書くとき、サーバー上で使っている名前と大文字と小文字は完全に同じでなければなりません.ここで設定が少し違うと、今後HTTP REQUESTがERRORを迎えます.
    import Foundation
    
    struct BalanceGame: Codable {
        let id: Int
        let firstChoice: String
        let secondChoice: String
        
        private enum CodingKeys: String, CodingKey {
            case id = "id"
            case firstChoice = "solution1"
            case secondChoice = "solution2"
        }
        
    }

    💻 HTTPリクエストの作成


    - Root/Services/HTTPClient.swift


    モデルを作成した後、ビューモデルを作成する前に、サーバに送信されるHTTPリクエストコードを作成します.
    初めてHTTPリクエストコードを見た時は何なのかさっぱり分からなかった.
    よく見てみましょう
    まず、コードは次のとおりです.
    import Foundation
    
    // 1
    enum NetworkError: Error {
        case badURL
        case noData
        case decodingError
    }
    // 2
    class HTTPClient {
    	// 3
    	func getBalanceGame(completion: @escaping (Result<BalanceGame, NetworkError>) -> Void) {
            // 4
            guard let url = URL.forRandomGameByName("balance-game") else {
                return completion(.failure(.badURL))
            }
            // 5
            URLSession.shared.dataTask(with: url) { data, response, error in
                // 6
                guard let data = data, error == nil else {
                    return completion(.failure(.noData))
                }
                // 7
                guard let balanceGame = try? JSONDecoder().decode(BalanceGame.self, from: data) else {
                    return completion(.failure(.decodingError))
                }
                // 8
                completion(.success(balanceGame))
                
            }.resume()
            
        }
    }
    Taskの順番で見てみましょう
    //1
    まず,Errorをenum値として定義する.これにより、後でエラーが発生したときにどのようなエラーが発生したかをすぐに知ることができます.
    //2
    その後、「HTTPClient」というクラスに「getBalanceGame」という関数を作成します.
    //3
    「@逃走」は非同期プログラミングであり,FlutterのAsync/awaitのような概念と見なすことができる.関数が終了した場合、Resultが成功したときにBanceGame、すなわちJSONデータパケットが入ったモデルを返し、失敗したときにエラーを返すと理解できます.
    //4
    「url」は、上記のurlextensionから取得し、バランスゲームのデータを受信するので、適切なパラメータを渡すことができます.ここでエラーが発生した場合、エラーのbadurlを返すことができます.
    //5, 6
    次に、Foundation Headerが提供するURLセッションのDataTaskメソッドを使用します.
    urlをパラメータとしてdataTaskに渡し、データがない場合はnoDataを返すコードを加えます.
    //7
    ここで、JSOndecoderを使用して受信したJSON値をグループ化し、グループ化したデータを「BanceGame」、すなわちモデルに格納します.DataTaskからのデータを使用して、エラーが発生した場合にエラー再復号エラーを返します.
    //8
    Task 7が正常に完了した場合、復号戻り値とともに正常に戻ります.
    このように記述されたHTTP要求(GET)関数は、ビューモデルで使用される.

    💻 ビューモデルの作成


    - Root/View_Models/GameViewModels/BalanceGameViewModel.swift


    ここで、MVVMモードのハイライトとして表示されるビューモデルを作成し、HTTP Requestでモデルの値をロードします.
    MVVMモードでは、前回のパブリケーションで説明した観測可能オブジェクトを使用してBanceGameViewModelというビューモデルを作成し、Balanceという変数を使用してモデルオブジェクトを空に宣言します(最初は何もないため).
    その後、HTTPClientオブジェクトを宣言し、上記のgetBalanceGame()関数を使用してデータを要求します.
    データの受信に失敗すると、出力エラーが発生します.データの受信に成功した場合は、モデルオブジェクトにデータをグループ化します.
    最後に、ID、First Choice、およびsecondChoiceが作成され、それらはViewモデルからモデルの変数値を受信する.
    コードは次のとおりです.
    import Foundation
    import SwiftUI
    
    class BalanceGameViewModel: ObservableObject {
        
        @Published var balance: BalanceGame?
        var httpClient = HTTPClient()
        
        init(balance: BalanceGame? = nil) {
            self.balance = balance
        }
        
        func getBalanceGameRandomly() {
            httpClient.getBalanceGame() { result in
                switch result {
                    case .success(let game):
                        DispatchQueue.main.async {
                            self.balance = game
                        }
                    
                case .failure(let error):
                    print(error.localizedDescription)
                }
            }
        }
        
        var id: Int {
            self.balance?.id ?? 0
        }
        
        var firstChoice: String {
            self.balance?.firstChoice ?? ""
        }
        
        var secondChoice: String {
            self.balance?.secondChoice ?? ""
        }
        
    }
    これで、ViewModelを使用してサーバに要求された値と、モデルに格納された値を使用できます.

    💻 ビューの作成


    - Root/Screens/Game/BalanceGameScreen.swift


    ビューを作成し、ビューにビューモデルオブジェクトを宣言し、init()関数を使用してビューモデルオブジェクトからHTTPリクエストを送信します.
    @ObservedObject private var balanceGameVM: BalanceGameViewModel
        
    init() {
        self.balanceGameVM = BalanceGameViewModel()
        balanceGameVM.getBalanceGameRandomly()
    }
    
    これで、モデルにサーバからのデータが含まれ、ビューにObserverObjectとして宣言されたビューモデルに必要なデータが含まれている可能性があります.
    では、これらのデータを使って画面に表示しましょう.
    画面での使い方はもちろん自由ですが、筆者は以下のようにデータをViewに表示します.
    Text(self.balanceGameVM.firstChoice)
    	.
        .
        .
    ビューから宣言されたビューモデルオブジェクト、上に宣言されたモデルからデータを受信する変数の友人を自由に使用できます.

    💻 終了:プロジェクト構造


    最後に、プロジェクト全体の構造を以下に示します.

    ScreenはM-V-VMモードではViewの役割を果たしており、画面を構成するViewフォルダは単独で存在するが、MVVMモードとの混同を避けるためスクリーンショットには参加していない.

    📌 後期&終了後...


    上にも触れましたが、HTTP Request Codeを初めて見たときは理解できませんでしたが、丁寧に1行1行勉強するのは、思ったほど難しくはありませんでした.
    現在はGETメソッドのみ使用されているが、今後CRUDがPOST、UPDATEなどのメソッドを使用できるように、基本的なURLセッションを学習し、強力なHTTP通信ライブラリAlamofreを使用する.
    知れば知るほど、SWIFTUIは面白くなる.まだまだ足りないことはたくさんありますが、この記事を見ている開発者に助けてもらいたいです.
    位置決め内容に誤りや不足点がある場合は、コメント()を歓迎します.
    printf("Thank You!\n");
    printf("Posted by Thirsty Developer\n");