ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
堅牢で再開可能なファイル転送の構築
URLSession がどのようにアプリの大容量ファイル転送とネットワーク中断からの回復を支援するかを学びましょう。HTTP ファイル転送の一時停止と再開のサポート、および再開可能なアップロードのサポート方法を確認し、アプリがバックグラウンドで中断されている場合でも URLSession を使用してファイルを転送するためのベストプラクティスを紹介します。
関連する章
- 0:00 - Welcome
- 2:42 - Explore the resumable downloads protocol in HTTP
- 4:23 - Pause and resume downloads with URLSession
- 7:50 - Pause and resume uploads with URLSession
- 9:45 - Explore the resumable uploads protocol in HTTP
- 12:46 - Add resumable uploads to SwiftNIO
- 16:11 - Use background URLSession
リソース
関連ビデオ
WWDC19
-
ダウンロード
♪ ♪
こんにちは Jonathanです Internet Technologiesチーム のエンジニアです URLSessionを使用して 堅牢で再開可能な ファイル転送を構築する 方法を検討します 小さな中断からすべての状況が 台無しになる可能性があり 大規模なファイルの転送が 大きな課題となって 最初からやり直しになる 可能性があります そして転送が 大きければ大きいほど 所要時間も長くなり 問題が発生する 可能性が高まります
その間ユーザーがアプリや Wi-Fiの範囲を離れることで 制御不能な非常に多くの ネットワークの問題が 発生する可能性があります これらの課題を解決し ユーザーに堅牢なネットワーキング の体験を提供する 方法を検討しましょう 最初の方法は 接続が中断されたときに ユーザーが進行状況を 維持できるようにする 再開可能な HTTPプロトコルです これにより時間と 帯域幅の無駄がなくなり 大量のデータを転送する場合の 強力なツールになります 再開可能なアップロード タスク用の新しいAPIを含め ダウンロードとアップロードの両方を URLSessionで再開する方法を説明します これらのAPIの背後にある メカニズムを理解することは アプリのデバッグや独自のサーバ サポートの構築にも非常に役立ちます そのため再開可能な プロトコル自体についても説明し アプリとサーバがHTTP経由でどのように 実現するかを正確に理解できるようにします
次にサーバについて説明します SwiftNIOを使用するサーバに再開可能な アップロードのサポート提供方法を学びます
最後にバックグラウンドの URLSessionsがユーザーと システムリソースを 効率的に利用しながら ネットワークの中断をどのように 処理できるかを確認します
URLSessionでのダウンロードと アップロードの再開について見てみましょう 最新のXcodeを ダウンロードしており 7ギガバイトのダウンロードが ほぼ完了するところですが 最後の最後で Wi-Fiが切れてしまいます ただしダウンロードは 一時停止されています Wi-Fiがオンラインに戻ったら 中断したところから ダウンロードを再開できます
多くの時間と数ギガの 帯域幅を節約できました 再開可能な ダウンロードは素晴らしいです それらはどのように 機能するのでしょうか? クライアントはGETリクエストを送信して サーバからダウンロードを取得します
応答でサーバは Accept-Rangesヘッダーを使用した 再開可能なダウンロード のサポートを通知します Accept-Ranges:バイトはサーバが このリソースの特定のバイトの 範囲リクエストをサポート していることを意味します サーバ応答にはある時点での リソースを一意に識別する ETagと呼ばれるもの も含まれています サーバ上のコンテンツが 変更されるとETagも変更されます
ではこのダウンロードが中断されると どうなるのでしょうか?
クライアントは部分的な ダウンロードデータを保存しているため 必要なのは最後の部分だけです これを達成するために 範囲リクエストを送信して まだダウンロードできていない バイトを把握できます リクエストはRangeフィールドを使って どのバイトかを示しています クライアントはリソースが 変更されていないか確認しますが そうでなければ新しいリソースから 保存されている古いリソースに データを追加することになります これを防ぐために If-Rangeフィールドには 前の応答から受信した ETagが含まれますが ETagが同じ場合は部分データのみを 送信するようにサーバに指示します
ETagが同じ場合 サーバは 206 Partial Content で応答します ここのContent-Rangeフィールドは この応答に含まれるバイトの範囲を示し ダウンロードを完了します
URLSessionは当初からRange リクエストを使用して ダウンロードタスクを一時停止および 再開するAPIを提供してきました 今後はアップロードも一時停止と 再開ができるようになりました これで進行中のタスクを 手動で一時停止するほか エラー処理を実行して 予期しない接続の問題から回復して 中断したところから転送を再開します これがどう機能するか まず ダウンロードから確認してみましょう ユーザーがダウンロードを 手動で一時停止し再開できる UIの作成を想定してみましょう Safariの例と同様に アプリはこのUIを所有していますが 内部でURLSessionを使用することで 部分的なダウンロードデータの追跡や ETag リクエストヘッダーなど すべての詳細を処理できます ダウンロードを開始するには 通常どおりダウンロードタスクを作成し resumeを呼び出して開始します ユーザーが一時停止ボタンをタップして ダウンロードを一時停止するには cancelByProducingResumeData を呼び出します 後でこのダウンロードを再開するには URLSessionはETagや 現時点のサイズ ディスク上の場所など 部分ダウンロードに関する情報が必要です これと他のメタデータは この関数から返される 再開データオブジェクトに 保存すると便利です 繰り返しますが再開データは 部分ダウンロードデータ ではないことに注意してください 再開データがnilの場合 再開可能なダウンロードの 1つ以上の要件が 満たされていないことを意味します それについては後ほど説明します 再開データがnilでない場合は 後で使用できるよう 保存する必要があります ユーザーが再開ボタン をタップしたときなど ダウンロードを再開するには この保存されたデータを downloadTask withResumeDataメソッドに渡します それはとても簡単です! このパターンはダウンロードを手動で 一時停止する場合に最適ですが URLSessionには予期しない接続の中断から 回復する方法も用意されています
ネットワークの問題により ダウンロードタスクが失敗した場合は エラー自体で再開データを確認できます ダウンロードを再開できる場合は エラーのuserInfoディクショナリに その再開データが含まれます URLError の downloadTaskResumeData プロパティを使用すると このデータに簡単にアクセスできます URLSessionで再開可能なダウンロードには いくつかの要件があります ダウンロードは本質的にデータを取得し 繰り返しても安全である必要があり URLSessionではダウンロードタスクに HTTP GETリクエストが必要です 他のスキームまたは メソッドはサポートされていません サーバはバイト範囲リクエスト をサポートする必要があり Accept-Rangesヘッダーを 使用してこれをアドバタイズします サーバはETag (Last-Modified) フィールドを提供する必要があります 応答内のリソースに使用されますが ETagの方が優先されます 最後に一時ダウンロードファイルは ディスク領域の不足に応じて システムによって削除されてはなりません
これらの要件を満たしていれば ダウンロードを手動で 一時停止および再開するか 接続中断から回復できます 再開プロトコルなしでは わずかな中断であっても 初めから転送を 再開しなければなりません これはアップロードにとって さらに大きな問題です アップロード速度は ダウンロード速度よりもはるかに遅く 再起動するとさらに多くの 時間とリソースが失われます iOS 17では再開可能なアップロード タスクの新しいサポートが導入されています とてもエキサイティングな進展です サーバが最新のプロトコル ドラフトをサポートしている場合 アップロードタスクが自動的に 再開できるようになりました まずは新しいAPIを見てみましょう 次に再開可能なアップロード プロトコルの詳細を見ていきます ダウンロードタスクと同様に アップロードタスクを作成し resumeを呼び出して開始します 一時停止にはアップロードタスクが ダウンロードタスクと同じ cancelByProducingResumeData メソッドをサポートします このタスクはサーバが 最新の再開可能な アップロードプロトコルをサポート しているか自動的に検出します サーバがサポートしている場合 後で使用できるように 再開データを保存できます
一時停止したアップロードを再開するには 新しいUploadTask withResumeData メソッドを使用します ここに示されているパターンはダウンロード タスクと同じであることがわかります つまりアプリでダウンロードを 一時停止および再開する 優れたエクスペリエンスを すでに作成している場合は ユーザー向けに再開可能なアップロード を簡単に実装することもできます ネットワークが一時的に中断されただけで サーバにまだアクセス可能な場合は URLSessionは自動的に アップロードを再開しようとします 追加のコードは必要ありません ただしネットワークやサーバが 完全にダウンした場合など その他の広範な 接続の問題が発生した場合は ダウンロードタスクと同様に 再開データのエラーを確認します URLSessionで再開可能な アップロードを確認できることを 非常に嬉しく思います 気に入っていただければ幸いです この機能を利用するには サーバが最新の再開可能な アップロードプロトコルも サポートしている必要があります
このプロトコルは現在開発中で 業界全体でIETFで 標準化する取り組みが行われています このプロトコルではクライアントは サーバのサポートを自動的に検出できます つまり URLSessionは 一番最初のリクエストで すべてのアップロードを 再開可能にできるよう試みられます サーバが再開可能なアップロード をサポートしていない場合 リクエストは通常の アップロードとして継続されます 通信回線でどう動くか見てみましょう
クライアントはまずアップロード エンドポイントにリクエストを送信します Upload-Incompleteフィールドは このクライアントが 再開可能なアップロードを サポートすると示しています 疑問符ゼロは 構造化フィールドの ブール値として知られ 値falseを表します すべてのアップロードデータが リクエストの本文に含まれています
サーバが再開可能な アップロードをサポートしている場合 サーバはクライアントのヘッダーを検出し 104情報応答を使用して サポート状況を周知します 104応答には再開URLを含む 場所フィールドが含まれています この再開URLはアップロードを 一意に識別するために使用され 接続が中断された場合クライアントは アップロードを再開する場所を認識します サーバは受信したアップロードデータを この一意の再開URLに関連付けます
アップロードが中断されずに 終了すれば問題はありません サーバが201を送信すると完了です ただしアップロードが中断された場合 クライアントとサーバは 再開可能なアップロード手順を実行します
サーバは再開URLの 部分アップロードを保存しましたが クライアントはサーバが実際に取得した データ量を判断する方法を必要とします これにはクライアントは HEADリクエストを再開URLに送信し サーバにアップロード オフセットを要求します このオフセットはサーバが 受信した実際のバイト数です
サーバはクライアントの特定アップロード のアップロードオフセットで応答します
そして最後にクライアントは サーバのオフセットを承認して 残りのデータを送信する必要があります このためにはクライアントは PATCHリクエストを 一致するアップロード オフセットを持つ再開URLに送信します このリクエストの本文には 指定されたオフセットから始まる アップロードデータが含まれています
これでクライアントは最終的に すべてのデータをサーバに送信し アップロードを完了します URLSessionのアップロードタスクで これらを無料で取得できます ここでサーバ側に簡単にアクセスし SwiftNIOを使って独自の再開可能な アップロードサーバを構築しましょう
すでにサーバ上で SwiftNIOを使用している方には このセクションが役立ちます 再開可能なアップロードは どのサーバでも実装できますが サーバがすでにSwiftNIO を使用している場合は サポートを非常に簡単に追加できる 新しいパッケージがあります 簡単な例を見てみましょう 詳しくない方のために説明すると SwiftNIOはアプリとサーバで動作する 非同期ネットワーク アプリケーションフレームワークです このサンプルコードではHTTP/2 サーバをセットアップしています サーバに2つのハンドラを追加しました コーデックはHTTP/2 フレームをサンプルハンドラが 理解できるリクエストに変換します 逆の方向ではサンプルハンドラから 応答を受け取り それをHTTP/2フレームに コーディングします ExampleChannelHandler はサーバの基本的なルーティングと ロジックを実行します 最初は通常の アップロードのみをサポートします 再開可能なアップロードをサポートするため サーバを変換することは簡単です
まずNIOResumableUpload プロジェクトをダウンロードし 依存関係として追加し コードにインポートします 次に再開可能なアップロード コンテキストを定義します これで再開URLを生成する際 どのアップロードエンドポイントを 使用するかをハンドラに指示します
最後に現在のハンドラを HTTP ResumableUploadHandlerでラップします これにより現在のロジックに基づいて 再開可能なアップロード手順が実行されます アップロードごとにランダムで 安全な再開URLが生成され これがアップロードデータ に関連付けられます 接続が中断された場合 ハンドラは部分的なデータを保持し 再開可能なすべての アップロード手順に応答します ワオ! わずか数行のコードで 再開可能なアップロードをサポートするため サーバを強化できました すでにサーバでSwiftを利用している場合は ぜひこれを試してみてください そしてどなたでも セッションの説明にあるリンクから オープンソースのサンプルコードを チェックしてみてください サンプルコードでは 新しいHTTPタイプも使用しており アプリとSwift on Server プロジェクト全体で 同じタイプを使用できます これらのデータ型は SwiftNIOと連携した オープンソースパッケージ としてリリースされました Swiftブログで詳細をチェックして フィードバックをお寄せください
すでにお気づきかもしれませんが 再開可能なアップロードプロトコルは 104ステータスコードを使用した 情報応答を利用しています 新しいHTTPタイプを使ってサーバ側で これらの応答を簡単にサポートできます アプリではURLSessionが 再開可能なアップロードの 104レスポンスを自動的に処理します さらにこのたび URLSessionは デリゲートメソッド― didReceiveInformationalResponse を提供するようになりました これによりアプリは 102 Processingや 103 Early Hintsなど 他の中間応答を処理できるようになります
再開可能なプロトコルは ネットワークの中断を軽減し 帯域幅を節約する優れた方法です バックグラウンドURLSessionは 大規模なファイル転送処理にも役立ちます ユーザーが旅先から巨大な 4K動画をアップロードするとします 接続が中断されても 可能ならアップロードを再開します すべてのエラー処理を 自分で行うこともできます またはバックグラウンドで 実行させることもできます
サーバがサポートしている場合 バックグラウンドではダウンロードタスクと アップロードタスクの 両方の再開を自動的に処理します
タスクが中断されると システムは間隔をあけて タスクを再開しようとします
タスクを再開できない場合 システムは自動的に タスクを最初から再試行します
もしもユーザーが山で 携帯の通信範囲を失ったり 吹雪でWi-Fiが使えなくなったら バックグラウンドセッションは 常に接続を待機するため デバイスが再び インターネットに接続した後 タスクがある時点で スケジュールされます
ユーザーはビデオをアップロードしている間 アプリやデバイスから 離れる可能性があります ユーザーはおそらく 他の作業をしている間 まだアップロードが続くと 期待するかもしれません
これには特にバックグラウンド セッションが必要です バックグラウンドタスクは システムによってスケジュールされるため アプリのプロセス外で実行されます つまりアプリがシステムによって 一時停止または終了されても ネットワークタスクは確実に継続されます 長い時間がかかりアプリを離れても 保持する必要がある 大規模なファイルの転送には バックグラウンドセッションを使用します
ユーザーがアプリで最高の体験を 得ることができるよう 緊急性の低いタスクが後で実行されるよう スケジュールすることもできます バックグラウンドセッションを使用すると ネットワークアクティビティを 効率的にする方法がいくつかあり ユーザーはリソースを節約できます
すぐに実行する必要がないタスクについては バックグラウンド設定で isDiscretionaryプロパティを trueに設定することを 検討してください これによりシステムがタスクを インテリジェントにスケジュールし 「ユーザーがWi-Fiに接続しているか」 「デバイスは電源に接続されているか」 「ネットワークは制限されているか」 などの要素を考慮できるようになります これは後の使用のためにアセットを ダウンロードしたり夜間のバックアップや 分析をアップロードしたりする際に 最適なオプションです
低データモードで帯域幅が 過剰に使用されるのを防ぐには allowsConstrainedNetworkAccess プロパティを falseに設定します 「Advances in Networking」 セッションもチェックして アプリで低データモードを サポートするヒントをご覧ください
バックグラウンドタスクを システムリソースを使用しない時間に 開始するよう スケジュールすることもできます 深夜は大規模なバックアップなど のタスクを行うのに最適です
システムのスケジューリング をさらに支援するために countOfBytesClientExpectsToSend および受信プロパティを設定できます これらのプロパティを利用することで システムがリソースを 最適に割り当てられるようになり そのメリットをユーザーに還元できます バックグラウンドセッションは すぐに実行する必要のない 大規模なファイル転送や アプリが一時停止されたときに 継続の必要がある転送に 最適なツールです 小規模なタスクや できるだけ早く実行すべきタスクは 標準のURLSessionを使用します アプリに再開機能を導入することで ユーザーのネットワークへの 信頼性を高めます SwiftNIOとHTTPタイプを確認して Swiftで最高のHTTPエクスペリエンス を作成しましょう 大規模なファイル転送には バックグラウンドセッションを使って下さい ユーザーが最も必要とするときに備えて リソースを保存する方法はたくさんあります ご視聴ありがとうございます 以下の素晴らしい ネットワークに関する下記のセッションも ぜひチェックしてください それではお疲れ様でした! ♪ ♪
-
-
4:53 - Pausing and resuming a URLSessionDownloadTask
let downloadTask = session.downloadTask(with: request) downloadTask.resume()
-
5:21 - Pausing and resuming a URLSessionDownloadTask
let downloadTask = session.downloadTask(with: request) downloadTask.resume() guard let resumeData = await downloadTask.cancelByProducingResumeData() else { // Download cannot be resumed return }
-
6:11 - Pausing and resuming a URLSessionDownloadTask
let downloadTask = session.downloadTask(with: request) downloadTask.resume() guard let resumeData = await downloadTask.cancelByProducingResumeData() else { // Download cannot be resumed return } let newDownloadTask = session.downloadTask(withResumeData: resumeData) newDownloadTask.resume()
-
6:34 - Retrieving resume data on error
do { let (url, response) = try await session.download(for: request) } catch let error as URLError { guard let resumeData = error.downloadTaskResumeData else { // Download cannot be resumed return } }
-
8:29 - Pausing and resuming a URLSessionUploadTask
let uploadTask = session.uploadTask(with: request, fromFile: fileURL) uploadTask.resume()
-
8:37 - Pausing and resuming a URLSessionUploadTask
let uploadTask = session.uploadTask(with: request, fromFile: fileURL) uploadTask.resume() guard let resumeData = await uploadTask.cancelByProducingResumeData() else { // Upload cannot be resumed return }
-
8:57 - Pausing and resuming a URLSessionUploadTask
let uploadTask = session.uploadTask(with: request, fromFile: fileURL) uploadTask.resume() guard let resumeData = await uploadTask.cancelByProducingResumeData() else { // Upload cannot be resumed return } let newUploadTask = session.uploadTask(withResumeData: resumeData) newUploadTask.resume()
-
9:22 - Retrieving resume data on error
do { let (data, response) = try await session.upload(for: request, fromFile: fileURL) } catch let error as URLError { guard let resumeData = error.uploadTaskResumeData else { // Upload cannot be resumed return } }
-
13:15 - Before resumable uploads in Swift NIO
NIOTSListenerBootstrap(group: NIOTSEventLoopGroup()) .childChannelInitializer { channel in channel.configureHTTP2Pipeline(mode: .server) { channel in channel.pipeline.addHandlers([ HTTP2FramePayloadToHTTPServerCodec(), ExampleChannelHandler() ]) }.map { _ in () } } .tlsOptions(tlsOptions)
-
14:06 - Add resumable uploads in Swift NIO
import NIOResumableUpload let uploadContext = HTTPResumableUploadContext(origin: "https://example.com") NIOTSListenerBootstrap(group: NIOTSEventLoopGroup()) .childChannelInitializer { channel in channel.configureHTTP2Pipeline(mode: .server) { channel in channel.pipeline.addHandlers([ HTTP2FramePayloadToHTTPServerCodec(), HTTPResumableUploadHandler(context: uploadContext, handlers: [ ExampleChannelHandler() ]) ]) }.map { _ in () } } .tlsOptions(tlsOptions)
-
15:48 - Informational responses in URLSession
protocol URLSessionTaskDelegate : URLSessionDelegate { optional func urlSession(_ session: URLSession, task: URLSessionTask, didReceiveInformationalResponse response: HTTPURLResponse) }
-
18:19 - Using background URLSession
// Configuring your background session let configuration = URLSessionConfiguration.background(withIdentifier: "com.example.app") configuration.isDiscretionary = true configuration.allowsConstrainedNetworkAccess = false let session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil) // Configuring your background task let backgroundTask = session.uploadTask(with: url, fromFile: fileURL) backgroundTask.earliestBeginDate = .now.addingTimeInterval(60 * 60) backgroundTask.countOfBytesClientExpectsToSend = 500 * 1024 backgroundTask.countOfBytesClientExpectsToReceive = 200
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。