ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
実践Combine
経時的に値を処理するためのAppleの新しい統一された宣言型フレームワークであるCombineについて知識を広げましょう。このセッションでは、エラーを適切に処理し、作業のスケジュールを設定し、Appに今すぐCombineを統合する方法を紹介します。
リソース
関連ビデオ
WWDC19
-
ダウンロード
(音楽)
こんにちは (拍手) Foundationチームの マイケル・リヒューです 本日のテーマは 今年リリースの― Combineフレームワークです トラクターの話ではないです 念のため
まず Combineとは何か 概要を簡単に説明します
コードには大抵 値やイベントのPublisherと―
その値やイベントを 受け取る― Subscriberが 数多く含まれています そして利害関係者が この2者の接続を確立します
その後 Subscriberは 時として― Publisherから値を受け取ると 宣言します
するとPublisherは下流に 自由に値を送れます この流れが止まるのは Publisherが送信をやめた時や― 何か失敗があった時や 購読がキャンセルされた時です
コールバックやクロージャなど 非同期通信の時に― こういう形の通信が ソフトウェアの随所で起こります このパターンこそが Combineです
経時的に値を処理できるAPIを 記述する― 統一的な抽象概念を Combineで定義します
値のPublisherを 詳細に見てみましょう
発表セッションで詳述しましたが おさらいです 値のPublisherは Publisherプロトコルに適合します
2つのassociatedtypeを 指定します Outputは発行する値の種類で 成功 失敗は関係ありません Failureは後で触れます
そしてSubscriberとの 接続の仕方を記述します 必須条件は associatedtypeの一致です これだけです 理論は これで十分でしょう セッションのタイトルどおり 実践します
実にクールな 魔法使いの友達に頼まれ― 魔法学校のアプリケーションを 共同開発します
このアプリケーションには― 魔法をダウンロードできる機能を 持たせたいのです デベロッパではない彼は― UIコンポーネント代わりの 絵をくれました
しかし彼はその機能を 実装できるコードを書けるので― 書くつもりです そこで私から Combineを使った― 必要な値の取得の仕方を 解説します このラベルに 魔法の名称を入れるのです
CombineではNotificationCenterが 通知をサポートします そこで魔法使いが送る通知の Publisherを作成します
この関数の戻り値の型が Publisherですが 重要なのはPublisherの OutputとFailureの型です
ここではOutputは“Notification” Failureは“Never” Publisherは 何度も言及しますので― 今後 そのOutputは上に Failureは下に表示することにします
これが通知のPublisher しかし ダウンロードした魔法を― 記述するデータが必要です
魔法使いは “データをuserInfo辞書に入れた” Combineにはmapという関数があり この通知を― 必要な形に変換できます これはSequenceの既存の操作と 非常に似ています このPublisherのOutputは“Data” Failureは“Never”です
mapはPublisherに作用し― 新しいPublisherの operatorを返します operatorは数多くあります
さらに魔法使いは データについて― “アプリケーションで定義済みの JSON型になる”と それなら別のoperatorで デコードを試せます tryMapです mapと同じですが― Failureに投げられたエラーを 変換する機能が足されています このoperatorのOutputが 魔法のPublisherになります Failureは Errorプロトコルに適合します
カスタム型のデコードは よくある作業なので― 処理するoperatorを提供します decodeです
PublisherのOutputは… (拍手) OutputとFailureの型は 同じままです
失敗するPublisherもあるので 対処法をお話しします
Combineでは― 失敗への適切な対応が 極めて重要です PublisherもSubscriberも 失敗を記述する可能性があります これを組み込んだのは Swiftと同じく― 規則ベースのエラー処理を 避けたかったから 他の言語では うまくいきませんでした そしてFailureを“Never”と 記述する型が多い 失敗が早い段階で処理されるという 期待を示しています
我々はNever以外のため 多くのoperatorを提供し― 失敗の対処と 必要なら回復も できるようにします
簡単なのは“失敗しない”と アサートすることです 返されたPublisherの Failureの型は“Never”になります 理由を見てみましょう
上流のPublisherと下流のSubscriberを 接続するoperatorが― assertNoFailureだとします このoperatorは 値を受け取れば送ります しかし上流から エラーが届いたら― プログラムがトラップします 魔法使いたちにとって 不本意な結果です
失敗に対処するoperatorは 豊富にあります assertNoFailure以外に 接続の再試行を試せるoperatorや― エラーの型を変えられる operator 特に有用なのはcatchで― 回復のPublisherを 定義するクロージャを提供でき― 上流のPublisherで 失敗が起きた場合に使います 仕組みを見てみましょう 先ほどと同様ですが 中間のoperatorがcatchです 今度も値は 下流のSubscriberに送られます しかし エラーが届いたら― 既存の上流の接続は 終了します
次に回復のクロージャを 呼び出し― 新しいPublisherを生成させ 購読すれば― 自由に値を受け取れます 新しいPublisherに置き換えることで エラーから回復できます コードで使ってみましょう
このクロージャは Publisherを返すことを期待しています
発行する値が既にあれば 特別なPublisherが定義されます とにかく値を発行するので Justです Combineが最初から備えている 多くのPublisherの1つです これで返されるPublisherの型は “Never”です
ここまで行った変換を 復習しましょう
出だしは通知のPublisher データをデコードすべく それをマップしました
その後 decode operatorを 利用して― データをユーザ定義型に変換 しかし デコードの失敗に備えて― Publisherを プレースホルダに置き換えました
しかし 待った
この回復のPublisherでは 通知は二度と見られません その接続は終了しました 出だしのPublisherと接続しながら デコードも試し― プレースホルダも 使いたいのです
そのためのoperatorも あります flatMapです
名前のとおり mapのような働きをします しかし新しいPublisherを 生成するようにと― 上流のPublisherが 値をよこします flatMapは この入れ子のPublisherの― 値の送信を細かく処理します 仕組みを見てから コードに戻りましょう
やはり値は 上流からflatMapに届きます flatMapはクロージャを呼び出し 値を新しいPublisherに変換 この場合はJustです
やはりdecodeとcatchが 続きます flatMapは新しいPublisherを購読し 値を下流に送ります
flatMapの 別の値はどうなるか
今度は このdecodeが エラーを投げると想像しましょう
それがcatchに届き 回復のPublisherに置き換えられます flatMapに返される Publisherです 従って この処理は 絶対に失敗しません
コードで使ってみましょう 先ほどの続きです データの流れの 最初のエラーを処理中でした
今度はflatMapが入りますし ごく単純な変換です やはりJustを使います mapからデコードしたデータで生成した 新しいPublisherです 入れ子スコープを flatMapに使い― 返し デコードし キャッチし flatMapに返します するとflatMapは購読します そのPublisherのFailureは “Never”になります
失敗を処理できたので 当初の目的を果たしましょう 魔法の名称の発行を 試すことです
やはり簡単に使える operatorです publisher(for:)です これで型安全なKeypathから ProduceMagicTrickに入り― 新たな 文字列のPublisherを 生成します
ここで 強力な機能を提供する operatorの種類をご紹介します
scheduled operatorです スケジューラのように― イベントをいつ どこに送信するか 記述できます
RunLoopとDispatchQueueが ネイティブにサポートします これらのoperatorは 例えば― delayはイベントの送信を 未来のある時まで延ばします
throttleはイベントの送信速度を 指定速度未満に抑えます
receive(on:)を使うと― 下流で受け取ったイベントが 特定のキューで送られます
これを使い 魔法の名称が メインキューで送られるようにします OutputとFailureの型は 変わりません scheduled operatorの特徴です 他の部分を復習しましょう
flatMapの先ですね
Publisher(for:)を使って 魔法の名称を抽出しました
最後に― receive(on:)で作業を メインスレッドに移します AppKitやUIKitに必要なUIの更新も これで完了です 発行された値は既に 正しいスレッドにあります
Publisherとoperatorとで 数多くの処理ができました operatorの作り方や 強力に型付けされた値を― 経時的に生成する 付加機能の提供
PublisherはJustのように 値を同期的に生成できます NotificationCenterのように 非同期的にも
しかし ここからは 反対側を取り上げます 値の受け取りです Subscriberを取り上げます
Publisherと同様 2つのassociatedtypeがあります Inputと 失敗の種類のFailureです
subscription value completionを受け取る― イベント関数を記述します
この関数の呼び出しの順番は 3つの規則に従っています
規則1 Publisherが― receive(subscription:)を 1回 呼び出します
規則2
Subscriberの要求で Publisherはゼロ以上の値を― 下流に提供できます
規則3 Publisherはcompletionを 1つ 送信できます そのcompletionは 発行が完了したか― 失敗したことを示します completionが届くと― 値はもう発行されません
この3つの規則を 要約すると― Subscriberは購読の成功を 1回 知らされ―
0個以上の値を受け取ります 発行の完了または失敗を 1回 知らされて終了の可能性も “可能性”と言ったのは それが任意だからです データの流れは 無限に続く可能性もあるのです
Combineでは 多様なSubscriberをサポート 仕組みをお見せします
Publisherの例に戻りますが 今 知る必要があるのは 使うPublisherの型です 場所を空けましょう
Subscriberを追加します 非常に簡単な購読方法です assign(to:on:) operatorを使い Keypath代入を追加しました これで上流のPublisherが 発行した値を― 指定オブジェクトのKeypathに 代入できます
非常に強力な値を どのプロパティにでも代入できます
このoperatorは購読を キャンセルするトークンも生成できます
そのキャンセルですが―
Combineに組み込みました Publisherが イベントの送信を完了する前に― 購読を終了できる方が 好都合だからです 購読の関連リソースを 解放したい場合は特にそうです ベストエフォート型ですが― Subscriberを削除する手段を 提供します
キャンセルを記述する 新しいプロトコルをご紹介します 極めて有用で便利な AnyCancellableです deinitが呼ばれた時 自動的にcancelを呼ぶので― 明示的にcancelを呼び出す回数が 劇的に減ります Swiftが提供している 強力なメモリ管理機能に頼るだけです
では2つ目の登録方法に 移ります
使うoperatorはsinkです 新しい値を受け取った時に 呼び出されるクロージャで― どんな副作用的な処理も できます
sinkはassignと同様 cancellableを返し― これを使って 購読を終了できます
3つ目の方法は ハイブリッドです Subjectです Publisherのようにも Subscriberのようにも機能します 値のマルチキャストを サポートしています 特に重要なのは 値を命令的に送らせることです 既存のコードベースを使う際に 最も重要です
その機能を見てから 使用例を見てみましょう
Subjectを使うと複数のSubscriberに 送信できることが多い上― 値を命令的に送れます 受け取った値を すべてのSubscriberに送信します
Publisherが生成する値も 同様です
Combineでは 2種類のSubjectをサポート Passthroughは 値を保持せず― このSubjectを購読すると 値が表示されます
もう1つはCurrentValueです 最後に受け取った値の履歴を 保持し― 新規のSubscriberに 追いつかせます
では今度も あのPublisherの実例です
このSubjectの生成は 好きな1つを選び― OutputとFailureの型を指定 コンストラクタを呼び出します
Subjectは 上流のPublisherを購読できます
Publisherのように― お話ししたsinkなどの operatorを呼び出して― 自身のSubscriberを 形成します
“please”のような値も 命令的に送れます
頻繁に届くSubjectを― データの流れに挿入するoperatorも 定義します 例えばPassthroughを挿入する share
Subjectは非常に強力です クールな使い道が 豊富にありますよ では 4つ目の 最後のSubscriberに移ります
SwiftUIと統合されています
SwiftUIはアプリケーション内の 依存関係を記述しさえすれば― あとは処理してくれます
Combineで言えばデータが いつ どう変更されたか― 記述するPublisherを 提供するだけです
BindableObjectプロトコルに カスタム型を適合させます
BindableObjectsの関連型は 1つです
発行の失敗がない Publisherなので― UIフレームワークでの作業に 最適です この言語の型システムが 上流のエラーの処理を― 強制してくるからです
最後に didChangeプロパティを 指定します これは型変更を通知するPublisherを 生成します これだけです 詳細はSwiftUI関連の講演を ぜひご覧ください すばらしい処理の数々を 詳しくご説明します ただ ここでも実例を 少々 お見せします
魔法学校のアプリケーションの モデルから始めて―
BindableObjectとの 適合性を追加 モデルオブジェクトの変更を Subjectで記述します Subjectは特定の種類の値を 示す必要はありません bodyメソッドから Combineが判断します このSubjectのOutputの型は “Void”にします
このSubjectは 柔軟性を高めます オブジェクトが変わったら 命令的に通知できるからです
今は2つのプロパティオブザーバで sendを直接 呼び出し― プロパティの変更時の モデルオブジェクトの変更を示します
次は このモデルを SwiftUIのViewにフックします 手順は まずモデルを ObjectBindingとして宣言します これでSwiftUIはPublisherを 自動的に見つけ 購読します 次はbodyプロパティから モデルのプロパティを参照します これだけです モデルの変更を知らせるたび SwiftUIは― 自動的に新しいbodyを生成します
多数の組み込み機能を組み合わせて 非常に強力なものを作成できます 非同期データフローが すばらしいやり方で単純化できるのです これらの優れた機能を 既存のアプリケーションに― さらに組み込む方法を ベンがご説明します (拍手) ありがとう よろしくお願いします
Combineは 組み合わせていく設計です マイケルの例のとおり― 小さなPublisherを さまざまに変換して― 最終的なPublisherを 作成しました
例を見てみましょう 魔法学校へのサインアップを アプリケーションに設定します いくつか条件があります まずユーザ名が有効か サーバに確認する
次はパスワード欄と パスワード確認欄の入力が― 必ず一致することと 8文字以上であることです
最後に すべての条件を満たしたら― UIを有効か無効にできること これは非同期動作の例です デバイス限定の同期動作が あるので― 組み合わせられるようにします 例を見てみましょう
まずInterface Builderで target-actionを― パスワード欄の 値変更プロパティに構築
それをコードで使うと― ユーザが入力したら シグナルを受け取れます 現在の値の テキストプロパティを受け取り ivarに格納します
しかし組み合わせたいのは 先にお話しした同期動作です その方法は? 本当に簡単です プロパティにPublishedを足せば Publisherを追加できます
Publishedはプロパティラッパーで Swift 5.1の― 新機能を使います
では簡単な例で 使い方を見てみましょう
Publishedは プロパティの前に追加されます
コード内では 前と変わりません
これを保持して 文字列値を取得します この場合 currentPasswordは “1234”です
ドル記号の接頭辞で参照すると 特殊化します ラップされた値に アクセスしています
Publisherで使うか それを購読する operatorが使えます この場合はsinkです
もう1つの“password”に そのプロパティを再設定したら―
Subscriberが 変更された値を取得 この人物は パスワード衛生に無頓着です
2つのPublisherを 同時に評価させます
足したのは Publishedプロパティと― 2つのPublisherと 発行された文字列です
検証済みのパスワードを 1つ発行するものが要ります
そのためのoperatorが CombineLatestです
2つのプロパティがあります そのプロパティラッパーを CombineLatestで参照でき―
どちらかが変われば シグナルを受け取ります 例えばパスワード欄に 既に入力されていて― 確認欄に入力するとします passwordAgainは変わりますが 入力されたpasswordは元のまま
次にクロージャで ビジネス要件を満たします “一致するか 8文字を超えているか”
不一致ならnilを返し― 他のシグナルと使用して フォームが有効かを判断します nilをシグナルとして使います
そして型を見ると― それまで踏んできた手順が 分かります 読み方はコードと同じです 2つの発行された文字列の 最新の値を結合し― 任意の文字列が 1つできました
しかし この悪いパスワードを 使わせないために― mapを追加したら? 型が変わります 2つの発行された文字列の 最新の値を結合し― 任意の文字列をもたらすよう マップしました ほぼすべてのユースケースの デバッグに最適です しかし これは API境界として通知しており― 他のPublisherと組み合わせたい そこで何が重要か絞ると― 失敗しない 任意の文字列のPublisherです そのためのoperatorが eraseToAnyPublisherです 任意の文字列“Never”の AnyPublisherを返します
型は変わっていません これでAPI境界のために欲しい契約を 通知でき― 実装の詳細をすべて隠せます
ここまでは最初のプロパティ つまり文字列を取得し― Publishedで 文字列のPublisherを追加しました
次にCombineLatestで Publisherの最新の値を結合し― ビジネスロジックを追加
次に悪いパスワードを mapを使って除外し 最後が―
eraseToAnyPublisherです API境界で 他のものと組み合わせるからです
1つ目のPublisherです
次に進みます
モデル化したい 非同期アクティビティです ユーザ名が有効か サーバに確認しますが― ユーザの入力は速いです
Publishedを 文字列プロパティストレージに追加し 値変更プロパティの target-actionをフックします ただ 少々特殊なのは― ネットワーク操作を 1文字ごとに発生させたくないこと スパム行為になります もう少し間隔を空けたい
そこでdebounceです debounceを使うと 値を受け取り かつ― 速く受け取らないウィンドウを 指定できます 例として 仕組みを見てみましょう 入力欄になる 上流のPublisherがあります そして中央にdebounce ユーザの入力が速いと― シグナルの送信も速い しかし1つのシグナルずつに できます
もっといいやり方もあります
そのウィンドウで入力し 値が最後に同じになるなら― 同じユーザ名が有効か 何度も確認しなくていい 例えば“Merlin”と 入力するとします “n”を消して また“n”を入力したら― やはり“Merlin”ですから removeDuplicatesで 処理できます ウィンドウ内で同じ値が 何度も発行されないようにします
ユーザ名プロパティに コードでPublishedを追加し―
debounceで シグナルの間隔を空け― 全く同じ値を除外します
しかし まだ非同期処理をしていません 目的はサーバに 有効か確認することです
そこで既存の関数 usernameAvailableを― Publisherとして使います
マイケルの例で 学びましたが― flatMapを使うと 新しいPublisherを返せます
どうやって呼び出すか
そのためにあるのが Futureです promiseを取るクロージャを 与えます promiseは成功か失敗の 結果を受け取るクロージャです
使うのはとても簡単です usernameAvailable関数を呼び出し 非同期処理が完了したら― この場合 promiseは“成功”になります 前と同じく 失敗したらnilです
手順の確認です 最初は単純なPublisher usernameでした debounceでシグナルの間隔を開き 同じものを除外
非同期ネットワーク呼び出しを行う 既存のAPIを次にFutureでラップ flatMapで データの流れをフォークしました
それがAPI境界なので eraseToAnyPublisherを使いました 今度は2つの カスタムPublisherを使います validatedPasswordと validatedUsernameです
この2つを組み合わせます
目的は2つのシグナル つまりデバイス限定シグナルと 非同期ネットワーク呼び出しで― UIを有効または無効にします やり方は分かっています
CombineLatestで 今の2つのPublisherを取得します 確認し オプションとして 完全な認証情報のタプルを返すか― そうでなければnilを返します
UIに配線するにはsignupButtonに アウトレットを配線します
この購読を保持するivarを 作成します ビューコントローラで 保持するためです フォームの表示中は ボタンを有効か無効にしたいので
そこで保存します ブール値にマップして― ボタンのisEnabledプロパティに 代入します
receive(on:)で メインのRunLoopに切り替えます UIコードに必要な操作です assign operatorで signupButtonのKeypathに代入します
部品がそろいました
最初は文字列を発行するだけの 3つの単純なPublisher
それから組み合わせを利用し― 小さな手順から積み上げて 最終的なチェーンを作成し 組み合わせてボタンに代入 これがCombineです
今すぐ始めましょう カスタムなPublisherにして― 小さなPublisherに分けられる ロジックを特定 組み合わせて すべてを1つにつないでいきます 追加的に導入できます 一気に変更する必要もなく 組み合わせられます Futureは 既に持っているものを― 導入できます
コールバックなどを 組み合わせられます
詳しくは入門編の講演や― SwiftUI関連の講演を ご覧ください AppKitのラボもあります 以上です (拍手)
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。