Open5

[Swift] SwiftUIでHTTP通信をする。

🍤🍤

この記事ではiOSにおいて、API通信を実現するコードの挙動を理解することを目標とする。
サンプルとして、Fetching Website Data into Memoryのコードを利用する。

URL Loading System

  • SwiftUIにおいてインターネット通信を行う際はこの仕組みを利用する。
  • ざっくり説明すると、URLSessionを作成し、タスク毎にURLSessionTaskを作成するという形。
  • URLSessionでURLを取得。
  • セッションを取得したら、URLSessionTaskを用いて色々処理をする。
  • httpsなど標準プロトコルや、ユーザーが作成したカスタムプロトコルを利用して、URLの示すWebページへアクセスを行う。
  • 読み込みは非同期(マルチタスク)で行われる。
🍤🍤

URL Session

  • ネットワークのデータ転送タスクのグループを調整するオブジェクト。接続の動作を定義する。
  • 接続の動作とは、1つのホストに同時に接続できる最大数や、接続にどのネットワークを使うのかというところを指している。
  • URLSessionには、基本的なリクエスト用のshared session(Configuration Objectを持たない)と、カスタマイズ可能なsession(Configuration Objectを持つ)がある。
  • アプリは、この API を使用して、アプリが実行されていないとき、または iOS ではアプリが中断されているときにバックグラウンドでダウンロードを実行するなどできるようになる。

sharedSession

  • URLSessionは基本的なリクエストのためにshared session(Configuration Objectを持たない)を持っている。
  • これはインスタンスが一つしか生成されないことが保証されている。(シングルトンである)
  • delegateのコールバックを用いて対話的な処理を行いたい場合は、後述する、Configuration Objectを持つ sessionを活用する。

Configuration Objectを持つsession3パターン

  • カスタマイズできるsessionのことをURLSessionConfigurationという。
  • 3つのタイプに分けることができる。
  1. default session:shared sessionとよく似た動きをする(シンプルな操作で利用する)。delegateを割り当てて、データをインクリメンタルに取得する(段階的に取得すること)ことができる。
  2. Ephemeral session:shared sessionと似ているが、キャッシュ、クッキー、認証情報を保存しない点が特有。セッション関係のデータはRAMに保存される。URLの内容をディスクに書き込むように指示した時は情報がディスクに保存される。
    エフェメラルセッションを使用する主な利点は、プライバシー保護にある。
    そのため、プライベートブラウジングモードで利用するなどの状況においては非常に役立ちがある。
  3. Background session:アプリが実行されていない間にバックグラウンドモードが利用できるようになるやつ。オーディオや位置情報の更新、リモート通知、ワークアウトのバックグラウンドモードなどが有効になる他、コンテンツのアップロードやダウンロードを実行することができるようになるやつ。

補足:キャッシュ、クッキーについて

  • キャッシュは表示したWebページで閲覧したデータなどを一時的に保存する機能。
    キャッシュ(英語: cache)は、CPUのバスやネットワークなど様々な情報伝達経路において、ある領域から他の領域へ情報を転送する際、その転送遅延を極力隠蔽し転送効率を向上するために考案された記憶階層の実現手段である
    キャッシュ (コンピュータシステム)
    キャッシュメモリ (cache memory) は、CPUなど処理装置がデータや命令などの情報を取得/更新する際に主記憶装置やバスなどの遅延/低帯域を隠蔽し、処理装置と記憶装置の性能差を埋めるために用いる高速小容量メモリのことである。-中略- 主に、主記憶装置とCPUなど処理装置との間に構成される。この場合、処理装置がアクセスしたいデータやそのアドレス、状態、設定など属性情報をコピーし保持。
    キャッシュメモリ
  • 次回表示する際読み込みが速くなるが、溜まりすぎるとスマートフォンなどの動作が重くなりがち。
🍤🍤

URLSessionTask

  • 4種のタスクが用意されている。
  1. Data tasks: NSDataオブジェクトを使用してデータ送受信を行う。サーバへの短めなリクエストのために用意されている。インタラクティブに操作できる。
  2. Upload tasks: バックグラウンドでのアップロードに対応した、ファイル送信向きタスク。
  3. Download tasks: バックグラウンドでのダウンロード、アップロードに対応した、ファイル送受信向きタスク
  4. WebSocket tasks: RFC6455のWebSocketプロトコルを使用し、TCP、TLSでのメッセージ交換に利用されるタスク。
  • ファイルのアップロードだけであれば、Upload Task、ファイルのダウンロードも必要であれば、Download Taks.
🍤🍤

以下sharedSessionでデータを受け取るコード。
startLoad() メソッドはURL のコンテンツをフェッチ(読み込み)するためのもの。
URLSession クラスのshared sessionインスタンスを使用して、その結果をcompletion handlerに渡すデータタスクを作成する。

import SwiftUI
import UIKit
import WebKit
/*Fetching Website Data into Memoryを参考に、
 Data taskを用いてウェブサイトからデータを取得するサンプルを作成する。*/

enum CustomError: Error {
    case handleClientError
    case handleServerError
}

func handleServerError() throws ->(){
    print ("handleServerError")
}
func startLoad() {
    //
    let url = URL(string: "https://www.example.com/")!
    //let webView: WKWebView!
    // data, response, errorがあれば却ってくる
    //タスクを作成するために、URLセッションのインスタンスを利用する。
    //今回は基本的なリクエスト用のshared session(Configuration Objectを持たない)を利用する。
    //  データを取得する最も簡単な方法はcompletion handlerを使用するデータタスクを作成する手法。
 //タスクはサーバーの応答、データ、そして場合によってはエラーを、completion handlerブロックに送出する。
   //completion handlerを使用するデータタスクを作成するには、URLSessionのdataTask(with:)メソッドを呼び出す必要がある。

    let task = URLSession.shared.dataTask(with: url) { data, response, error in
        
        print("data", data as Any)
        print("response", response as Any)
        print("error", error as Any)
        //data, response, errorを出力してみる。
        if let error = error {
            print(error.localizedDescription)
            return
        }
/*
completion handlerは3つのことを行う必要がある。
 1.error パラメータが nillだったらトランスポートエラーが発生したことになるので、適切に対処する。
 2. レスポンスパラメーターを確認し、ステータスコードが成功を示していること、MIME タイプ(受け取った情報の形式)が期待される値であることを確認。
 もしエラーが発生していたら、サーバーエラーを処理して終了する。
 3.必要に応じてデータインスタンスを使用する。
*/
        //もしもdelegateコールバックを通じて転送と対話を行いたい場合は、Configuration Objectを有するsessionを作成する必要がある。
        // httpResponseが200~299以外だったらreturnする
        // 200 : 成功ということ
        // 404 : URL見つからない
        // 500 : サーバー側エラー
        guard let httpResponse = response as? HTTPURLResponse,
              (200...299).contains(httpResponse.statusCode) else {
            CustomError.handleServerError
            return
        }
        //dataTaskの完了Closureに渡される URLResponse を HTTPURLResponseにキャスト
        //statusCodeプロパティを参照する。
        // responseのmimeTypeがtext/htmlで
        // 色々なmimeTypeがあるので調べておくこと
        if let mimeType = httpResponse.mimeType, mimeType == "text/html",
           // dataがあれば
           let data = data,
           // 文字コードutf8でデータを読み取り
           let string = String(data: data, encoding: .utf8) {
            //completion handlerの呼び出し
            DispatchQueue.main.async {
                // webViewにその文字を表示する
                print ("\(string)を取得したよ。")
                //               webView.loadHTMLString(string, baseURL: url)
            }
        }
    }
    task.resume()
}



上記の流れをとても簡単に表現すると

  1. shred sessionでURLSessionを作成する。
  2. dataTaskを作成する。
  3. dataTask生成時のCompletion handler内で、data, response, errorを取得する。
  4. responseの内容をチェックする
  5. errorの内容をハンドリング
  6. dataの処理を行う
    という感じになる。
🍤🍤
URLSessionDelegateの定義
@available(iOS 7.0, *)
public protocol URLSessionDelegate : NSObjectProtocol {

    
    optional func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?)

    
    optional func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)

    optional func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?)

    
    @available(iOS 7.0, *)
    optional func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession)
}