以前の投稿でWeb上のAPIからデータを取得する部分のサンプルコードをご紹介しましたが、そもそものSwiftUIがWebにアクセスする仕組みについて正しく理解をしたいと思いましたので、本投稿で整理したいと思います。
URL Loading System
SwiftUIにおいてインターネット通信を行う際にはこの仕組みを用いることになります。
You use a
https://developer.apple.com/documentation/foundation/url_loading_systemURLSession
instance to create one or moreURLSessionTask
instances, which can fetch and return data to your app, download files, or upload data and files to remote locations.
上記にあるように、URLSessionを一つ作成し、その配下にURLSessionTaskを1つ以上作成する、というのが基本的なコンセプトのようです。以下の図がわかりやすそうです。
つまり、流れとしては以下のようになります。
- URLSessionを作成
- タスク毎にURLSessionTaskを作成
ここで、URLSession、URLSessionTaskについて概要を整理していきます。
URLSession
URLSessionには以下のタイプがあるようです
- 基本的なリクエスト用のshared session(Configuration Objectを持たない)
- カスタマイズ可能な作成したsession(Configuration Objectを持つ)→ URLSessionConfigurationを活用
なお、公式ドキュメントでは、まずはConfiguration Objectを持たないshared sessionから始めることを推奨しています。これは、後述するshared sessionの利用方法が最もシンプルである、ということも大きな理由のようです。
URLSessionConfiguration
”カスタマイズ可能なsession”を作成する際にこちらを活用することになり、カスタマイズのパターンは以下の3つが用意されています。
- Default session:delegateを用いた段階的なデータ取得が可能
- Ephemeral session:キャッシュ、クッキー、認証情報をディスクに書き込まない
- Background session:アプリケーションが動作していないときの、バックグラウンドでのコンテンツのアップロードやダウンロードが可能
URLSessionTask
URL Session Taskに関しては、4種類のタスクが用意されている旨、URLSessionのドキュメントに記載されています。
- Data tasks: NSDataオブジェクトを使用してデータ送受信。ちょっとした(特にインタラクティブな)リクエスト向きタスク
- Upload tasks: バックグラウンドでのアップロードに対応した、ファイル送信向きタスク
- Download tasks: バックグラウンドでのダウンロード、アップロードに対応した、ファイル送受信向きタスク
- WebSocket tasks: RFC6455のWebSocketプロトコルを使った、TCP、TLSでのメッセージ交換向きタスク
ファイルのアップロードだけであれば、Upload Task、ファイルのダウンロードも必要であれば、Download Taks、サーバからクライアントに対して更新のポーリングが必要であれば、WebSocket Task、それ以外であれば、Data Task、といった使い分けでしょうか。
RFC6455
task state
URLSessionTaskは以下の4つの状態を持ち、タスクが生成された時点ではsuspended状態になるため、使用開始するためには、忘れずにresumeを用いてrunning状態にすることが重要です。状態別の概要は以下の通りです。
- running: 現在実行中のタスク。タイムアウトの影響を受ける
- suspended: タスクが再開されるまで処理を行わない。タイムアウトにはならない
- canceling: cancelメッセージを受信したタスク。タイムアウトにはならない
- completed: cancel以外で完了したタスク。errorプロパティはnil。タイムアウトにはならない
task resume
Newly-initialized tasks begin in a suspended state, so you need to call this method to start the task.
https://developer.apple.com/documentation/foundation/urlsessiontask/1411121-resume
実装に向けて
以上のことから、下記投稿で紹介したような単純な通信は以下の組み合わせで実現するのがよさそうです。
- URLSession: shared session
- URLSessionTaks: Data task
shared sessionの利用
Unlike the other session types, you don’t create the shared session; you merely access it by using this property directly. As a result, you don’t provide a delegate or a configuration object.
https://developer.apple.com/documentation/foundation/urlsession/1409000-shared
上記の通り、shared sessionであれば、sessionの作成は不要で、プロパティにアクセスするだけでよいとのことです。
URLSessionTaskの利用
The URLSessionTask class is the base class for tasks in a URL session. Tasks are always part of a session; you create a task by calling one of the task creation methods on a URLSession instance. The method you call determines the type of task.
https://developer.apple.com/documentation/foundation/urlsessiontask
URLSessionTaskはURLSessionの一部に含まれていて、使いたいタスクタイプを指定すればよさそうです。
Data taskの指定
dataTaskの定義は以下のようになっていますので、URLオブジェクトを渡してやればよさそうです。
func dataTask(with url: URL) -> URLSessionDataTask
ここまででURLSession, URLSessionTaskの概要と、その利用方法が概ね理解できたと思います。次に実際の実装の検討に移りますが、AppleのDeveloperサイトにちょうどよい記事がありましたので、そちらを参考にすすめていこうと思います。
Fetching Website Data into Memory
こちらは、Data taskを用いてウェブサイトからデータを取得するサンプルとなっています。少し説明を読んでみます。
The simplest way to fetch data is to create a data task that uses a completion handler. With this arrangement, the task delivers the server’s response, data, and possibly errors to a completion handler block that you provide.
https://developer.apple.com/documentation/foundation/url_loading_system/fetching_website_data_into_memory
CompletionHandlerを使うことで、Data taskは以下を取得してくれるようです。
- サーバからのレスポンス
- データ
- 発生したエラー
Completion Handlerについて
サンプルからこの部分に該当するコードは以下になります。
let url = URL(string: "https://www.example.com/")! let task = URLSession.shared.dataTask(with: url) { data, response, error in
下記投稿で作成したコードに、data, response, errorを出力する部分を追加して中身を見てみることにしました。
func getData() { guard let url = URL(string: "https://jsonplaceholder.typicode.com/todos/1") else { return } URLSession.shared.dataTask(with: url) {(data, response, error) in do { print("data", data as Any) print("response", response as Any) print("error", error as Any)
data Optional(83 bytes) response Optional(<NSHTTPURLResponse: 0x6000023991c0> { URL: https://jsonplaceholder.typicode.com/todos/1 } { Status Code: 200, Headers { Age = ( 8728 ); ... error nil
dataはAPIのレスポンスデータ部分、responseはHTMLのレスポンスがすべて含まれている感じです。
まとめ
特別複雑でないWeb通信を実現するためには、以下を実施すればよさそうです。仕組みがわかってしまえば数行のコードでWebにアクセスできますね。
- shred sessionにアクセスし、dataTaskを生成
- dataTask生成時のCompletion handler内で、data, response, errorを取得
- responseの内容をチェック
- errorの内容をハンドリング
- dataの処理(デコードなど)