ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
File Providerを使用したiOSにおけるデスクトップクラスの同期の実現
File Provider Extensionを作成し、iPhoneやiPadのApp内でファイルをより高速かつ効率的に同期する方法を紹介します。File Providerチームの協力のもと、iOSに対応した最新のFile Providerを構築する方法について学びましょう。ファイルのシームレスな同期、アップロード、ダウンロードに対応するAppの設計方法を紹介し、ファイルプロバイダをステートレス化して、予期しない状況にも対応できるよう強化する方法を説明します。このセッションを最大限活用するには、macOSのFile Providerの使用経験があることが推奨されます。
リソース
- File Provider
- File Provider UI
- Sending notification requests to APNs
- Synchronizing files using file provider extensions
関連ビデオ
WWDC21
-
ダウンロード
こんにちは Johannes Fortmannです Cloud File Providerチームに 所属しています 本日は 新しいFile Provider APIの 使用方法について説明します iOS 16に追加されたこのAPIでは デスクトップクラスの同期がiOSで実現します まずAPIを紹介し その後に ファイルプロバイダの目的について 簡単におさらいします 次に Appを設計するにあたっての 最適なアプローチについて説明し iOSで特に重要となる ベストプラクティスも 紹介します 最後に iOS上で実行するプロバイダの 簡単なデモをお見せします Big Surには Macとファイルを同期するための 宣言型APIが導入されました このAPIは多くの クラウドベンダーが採用しており 成功を収めています 私のチームでは継続的に このAPIの改良に取り組んでおり iOS 16にも対応できたことを 嬉しく思います このAPIを使用すると 「デスクトップクラスの同期環境」 と呼ばれるものが iOS向けAppで実現します これが何を意味するのか? iOSのAppが よりパワフルになるにしたがい ファイルシステムの共有エリアへの アクセス機能がさらに重要になります こうしたパワフルなAppでは ユーザーは あらゆる種類の ファイルシステムオブジェクトに アクセスできることを求めます Appからフォルダにアクセスしたり 新しいファイルを作成したりできる 必要があります こうした機能を実装すると同時に 一貫性を維持する必要もあります この一貫性とは何を意味するか? iOSではこれまで 消費電力に関する懸念から バックグラウンドでの実行時間が 制限されてきました ところがそれと同時に ユーザーからは 変更内容がバックグラウンドで アップロードされることも求められます この問題を解消するため 最新のFile Provider APIが 導入されました 基本的なレベルではデベロッパが App Extensionを実装し これによってアイテムの列挙 コンテンツのフェッチとアップロード リモートでの変更があった場合に アイテムリストの更新を処理します デベロッパが提供する情報を公開する作業や 一貫性を維持する作業は システムが担います システムが行う重要なタスクの1つは エラーを追跡し 必要に応じて再試行することです コンテンツのフェッチなどの 一部の処理の場合 再試行はユーザーが必要に応じて 自ら行います ユーザーは ダウンロードが 完了するのを積極的に待ち 多くの場合 進捗状況バーを目で追っています 一方 アップロードの場合 スケジュール管理が必要になります システムがディスク上のアイテムの 状態を追跡することにより 更新されたコンテンツが確実に アップロードされます 進捗状況とエラーが追跡され 必要に応じて アップロードが再試行されます アップロード中における一貫性の維持も 複雑なトピックです システムは ファイルコンテンツのクローンを管理し アップロード中でも そのファイルに正常にアクセスでき 正確なデータが表示されるようにします この処理の際 システムは同時に 複数のAppが同じファイルに アクセスした場合にも ローカルバージョンの 一貫性が維持され リモートサーバからの同期が 反映されるようにします これは APFSの機能および ファイルコーディネーションを使用して 透過的に処理されます モバイルデバイスでは ストレージ容量が 大きな制約となっています システムはAPFSの機能を使用して ローカルファイルの状態の変更を 詳細に追跡します これにより ディスクの使用率と 最近使用されていないという状態に基づき ローカルでの変更履歴がないファイルを 透過的に退避させることができます 完全にアップロードされたファイルは 「設定」のストレージ管理のパネルで Appに対してカウントされません ここまでは システムとExtensionについて お話しました 次は これらとAppとの 関係についてお話します 関心の分離を厳格に適用することを おすすめします システムは ディスク上の構造を管理し タスクのスケジュールを管理する役割を担います Extensionは それらのタスクを実行して 上下双方向の同期を行う役割を担います システムは ファイル階層に 関するすべての状態および 同期を必要とするアイテムを追跡します これは Extensionを大幅に 軽量化できることを意味します Extensionでアイテム固有の状態を 追跡する必要は一切ありません App側で同期を処理する 必要もありません 理論的には Appはサーバと 一切通信する必要がありません Appはその代わりに Extensionとやり取りします これには2つのメカニズムが使われます Appは システム上のほかのAppが 行うのと同様に Extensionと間接的にやり取りできます あらゆる管理対象アイテムのファイルの URLをフェッチするためのAPIがあります これにはルートも含まれます これにより これらの場所は ファイルシステムの 標準的なAPIを使用して アクセス可能になります もう1つは Extensionへの直接的な XPCサービス接続を Appでリクエストする方法です これは特に ファイルシステムの操作として 表現できないタスクを 処理する場合に便利な方法です ファイルの共有や競合の解消などが これに該当します 両メカニズムとも File Provider UI Extensionで 使用することができ 統合ポイントをファイルApp内に 追加することができます 次に ステートレスプロバイダで特に重要となる 3つの点について説明します まずはアップロードについてです すでにお話したように システムはアップロードを管理し アップロードの実行にかかる時間を Extensionに付与します ここで重要なのは アップロードが実際に進行していることを システムが継続的に認識できるよう 進捗状況を報告することです アップロードタスクが進行していない場合 タスクはキャンセルされます その場合は アップロードをきちんと停止するための 猶予時間がシステムから与えられますが キャンセルに時間がかかりすぎた場合は Extensionが終了されます それではコードを見てみましょう キャンセルハンドラを実装するには タスク固有メソッドからの進捗の戻り値に それをセットするだけです アップロードの場合 これはmodifyItemになります ハンドラでは 実行していた実際のアップロード処理を キャンセルします もちろん 完了ハンドラを呼び出して キャンセルエラーが発生したことを 通知する必要もあります 便宜上 このコード例では 非同期タスクキャンセルを 使用していますが 手動で完了ハンドラを 呼び出すことも可能です 次は 下方同期パスについて説明します ユーザーがファイルを操作した際に メインのAppが実行中でない場合は 変更内容をサーバから受け取れません その場合にもリモートでの変更を システムに通知するには プッシュ通知を 実装する必要があります PushKitを使用すると ファイルプロバイダが特定の プッシュタイプを利用できるようになります このようなプッシュ通知は Extensionから直接登録できます サーバで 適切に定義されたペイロードを 使用してプッシュ通知を送信します システムがプッシュ通知を受信し 必要に応じて現在の状態を更新します ほかのタイプのタスクと同様に バッテリーの状態や ユーザーがファイルを開いているなど 状況に応じてシステムが実際の更新を 遅らせる場合もあります この最後の点は 強調しておきたいと思います システムは Extensionが報告した フォルダ階層を管理します このことにより システムは フォルダ階層全体を供給できます ここでExtensionは ほかに何もする必要がありません 最新のファイルプロバイダの場合 これはデフォルトで有効になっています では この機能で可能となる ワークフローについて 簡単なデモをお見せします デバイスに このセッションの サンプルコードを用意しました サンプルコードをiOSに移植して サーバへのログインを処理する iOS Appを作成しました ただしExtensionは macOSバージョンから ほとんど変更していません 今 iPadでこのサンプルコードを 実行しています 右側ではファイルAppが開き すでにファイルが同期しています また フォルダ選択機能の 利点を活かしたAppも作成しました このAppは フォルダ内のすべての画像に セピアフィルタを適用します このようなAppでは フォルダへのアクセス機能が有効です これは 個々のアイテムに対する 個別操作を必要とせず フォルダ内のすべてのアイテムを 操作できるためです デスクトップクラスの同期環境では フォルダをファイルAppから バッチエディタに 簡単にドラッグできます では フォルダとファイルを表示して この流れを見てみましょう ボタンを押すと すべての写真がダウンロードされ 加工されます 加工が終わると ファイルは自動的にアップロードされます Extensionによって報告される アップロードの進捗状況は ファイルAppの下部に表示されるため ユーザーは状況を把握できます このような機能を Appに実装する方法を見てみましょう まずはアイテムのドラッグ機能を実装します ドラッグを開始可能にするため onDragメソッドを実装します このメソッドから NSItemProviderが返されます ドラッグするファイルのタイプを用いて ファイル表示方法を itemProviderに登録します この例ではフォルダです getUserVisibleURLメソッドを使用して URLをフェッチします ドラッグ先にはonDropを実装し ビューを ドロップターゲットとしてマークします 次に ファイルのURLを 適切なアイテムプロバイダから読み込みます これは このSandbox外に存在する ファイルとなります Appからこのファイルにアクセスするには URLのセキュリティ範囲を消費して リリースする必要があります 次は これ以降の手順についてです サンプルコードを更新して iOS Appを追加しました これをダウンロードして シンプルなステートレスプロバイダの セットアップを実験してみてください 一から始める場合は 更新されたXcodeテンプレートを 必ず使用してください これには 作業を開始するための 基礎が用意されています ファイルプロバイダや その実装方法について 詳しく学びたい場合は WWDC21の 「macOSにおけるFileProviderを使用した ファイルのクラウドへの同期」 を参照してください ご視聴ありがとうございました 皆さんの成果と iOSデバイス対応の 信頼性の高いプロバイダを 楽しみにしています
-
-
6:04 - Implement a Progress Cancellation Handler
// Implementing a progress cancellation handler public func modifyItem(_ item: ..., completionHandler: (..., Error?) -> Void) -> Progress { let progress = Progress() let uploadTask = Task { do { // ... try Task.checkCancellation() // ... } catch let error { completionHandler(nil, [], false, error) } } progress.cancellationHandler = { uploadTask.cancel() } return progress }
-
6:53 - Register for Push Notifications
// Registering for push notifications import PushKit let pushRegistry = PKPushRegistry(queue: queue) pushRegistry.delegate = self pushRegistry.desiredPushTypes = Set([PKPushType.fileProvider]) ... // On the server: push // // { // "container-identifier" = "NSFileProviderWorkingSetContainerItemIdentifier" // "domain" = "<domain identifier>" // } // // with topic "<your application identifier>.pushkit.fileprovider"
-
8:53 - Drag and Drop: Implement Dragging
// Sending out drags var body: some View { Text("🥐") .onDrag { let itemProvider = NSItemProvider() itemProvider.registerFileRepresentation(for: .folder, openInPlace: true) { completionHandler in self.manager.getUserVisibleURL(for: folderItemID) { fileURL, error in guard let fileURL = fileURL else { completionHandler(nil, false, error) return } completionHandler(fileURL, true, nil) } return Progress() } return itemProvider } }
-
9:24 - Drag and Drop: Implement Dropping
// Receiving drops var body: some View { Text("🥬") .onDrop(of: [.folder], isTargeted: $dropTarget) { providers in guard let prov = providers.first(where: { provider in !provider.registeredContentTypes(conformingTo: .folder).isEmpty }) else { return false } prov.loadFileRepresentation(for: .folder, openInPlace: true) { url, inPlace, err in guard let url = url else { return } Task { url.startAccessingSecurityScopedResource() // use URL url.stopAccessingSecurityScopedResource() } } return true } }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。