ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Swiftの型推論を利用する
Swiftは、型の安全性を損なうことなく、クリーンで簡潔なコードを書くために役立つ型推論を使用しています。型推論のパズルを解くためにコンパイラがどのようにヒントを探し当てるかをお伝えします。また、コンパイラが解決できない時にはどうするか、コンパイル時間において間違いを理解し、訂正するのに役に立つ、Xcode 12のエラートラッキング統合方法についてもお伝えします。
リソース
関連ビデオ
WWDC20
-
ダウンロード
“ようこそ WWDC2020へ”
こんにちは ようこそWWDCへ “Swiftの型推論を利用する” こんにちは Swiftコンパイラチームの技術者ホリーです “Swiftの型推論を利用する”へようこそ Swiftは型推論を使うことで― コードの安全面を損なわずに 構文を簡略化できます このセッションでは “型推論を いつ活用するか” “型推論が コンパイラでどう働くのか” “型推論をSwiftとXcodeで使って” “コードのコンパイラエラーを 修正する方法”を学びます 本題の前に“型推論”をおさらいします 型推論を使うと コンパイラがソースコードの 文脈から詳細が特定できる場合は― 明白な型アノテーションと 冗長な詳細を省略できます この例では ソースコードに “x”の型は書かれていません しかし 代入で得られた値を基にした文字列― すなわち文字列リテラルだと コンパイラは判断できます “x”の後ろにコロンで型アノテーションを書き 明確にすることもできます またas演算子で 文字列を強制型変換することもできます 例は短いですが SwiftUIプロジェクトのように― コードが型推論に大きく依存する場合に 威力を発揮します SwiftUIアプリケーションの 複雑なインターフェイスの多くは― 小さな再利用可能ビューの組み合わせです こうしたコードは再利用できる構成要素の 呼び出し領域で型推論に依存します 最初に呼び出し領域で型推論を活用する― SwiftUIアプリケーションのコードを 見ていきます 次にコンパイラが 型推論のパズルを解く仕組みを学びます 最後に SwiftコンパイラとXcodeを使って― コード内のコンパイラエラーを 修正する方法を学びます これは作成中のスムージー注文アプリケーション “Fruta”です 右手のプレビューで スムージーリストが表示されていますが―
スムージーを検索できる機能を追加したいです 現在のリストの実装方法を見てみます
bodyは スムージーの配列から SwiftUI Listを作成し― SmoothieRowViewに 各スムージーをマッピングしています 検索機能を追加するには― 検索する文字列を基に スムージーにフィルタをかけます そのため 文字列を保存する Stateプロパティが必要です FilteredListという 再利用可能ビューを構築しました リストに似ていますが― フィルタ方法を特定するイニシャライザに 2つの引数を渡せます
1つ目はフィルタをかけたい スムージーのプロパティの“KeyPath” 2つ目は そのプロパティを基にしたリストに スムージーを含むか演算する機能です
例では スムージーをtitleでフィルタにかけて searchPhraseを部分文字列に持つ スムージーを含みます hasSubstringメソッドは titleがsearchPhraseを持つか確認し― searchPhraseが空の文字列だった場合 trueで返します
明白ではありませんが― FilteredListイニシャライザの呼び出しは 型推論に強く依存しています 何が このコードの型推論への 依存を引き起こすのか そしてコード簡略化のための 呼び出し領域の型推論の仕組みを説明します 呼び出し領域から省略されている 付随情報を理解するため― FilteredListの宣言と そのイニシャライザを見てみます FilteredListは 汎用ビューで― どんなタイプのデータや内容でも 顧客に応じて使えるはずです ジェネリクスによって柔軟性を 実現しました 導入した3つのパラメータはプレースホルダで 呼び出し領域で実際の型に変換されます この型は正式には“具象型”といい― 呼び出し領域で特定されるか もしくはコンパイラが判断します 今回 Elementは データ配列の要素の型のプレースホルダ FilterKeyはフィルタがかかるElementの 特定のプロパティの型のプレースホルダ RowContentはリスト表示される ビューの型のプレースホルダです イニシャライザのパラメータリストに 以上の3つの型パラメータを使います この型パラメータが表示される位置が― 呼び出し領域で 型推論を活用できる場所です コンパイラが 置き換える型パラメータを 判断する引数を与えます その仕組みは 後ほど説明するとして― リスト構築時のパラメータの型に 焦点を当てます まず FilteredListは マップするデータ― Elementの配列で 初期値に設定されます
次のパラメータはフィルタがかかった Elementのプロパティで― Elementのベース型のKeyPathと FilterKeyの値型です
次のパラメータは isIncludedクロージャで― この関数の型にFilterKeyを入力すると Boolを返します FilteredListのプロパティに 保存するので― escaping属性にします
最後のパラメータはデータ要素を ビューにマップするクロージャで― この関数の型にElementを入力すると RowContentを返します このクロージャも 保存するために escaping属性にします
このパラメータは“ViewBuilder”とマークされ クロージャの引数で SwiftUI DSLの構文を実現します SwiftUI DSLは複数の子ビューを― クロージャのbodyでリスト化することで 宣言できます ViewBuilderは親ビュー用に 子ビューを1つにまとめます これがFilteredListの完全なイニシャライザです FilteredListが初期化されると― 型パラメータが具象型に置き換えられます SmoothieListの呼び出し領域と イニシャライザを並べて― 型推論に依存している様子を確認しましょう
呼び出し領域は とても簡素で― 明白な型アノテーションは書かれていません それでもFilteredListの各型パラメータに対し 具象型を判断するための情報を コンパイラは得ています コード上で すべての型を明確に定義したら 呼び出し領域は どうなるでしょうか 型パラメータのプレースホルダを使い― 具象型を埋めるため どこで型推論が必要か明記します まず FilteredListの3つの型パラメータは― “FilteredList”の後ろの呼び出し領域に カッコで明記されます
最初のパラメータはElementの配列ですが “smoothies”の引数の後に as演算子で特定します
2つ目のパラメータでは “Element”はKeyPathのベース型です KeyPathリテラルのベース型を― バックスラッシュとドットの間に明記します
KeyPathの型全体は 後ろにas演算子を添えて特定します
次にisIncludedクロージャの型は― クロージャのbodyに 型アノテーションで明記します 最後にRowContentクロージャの型も― bodyに型アノテーションで特定します
私が最初に書いたソースコードは 冗長な型アノテーションが多く― 型推論で埋めるプレースホルダも 多く含まれていました そこで各プレースホルダに何が入るか 手動で確認せずに― 型推論を活用して 各型を特定することにしました つまり型推論を使えば ソースコードを早く書けるのです 型のスペルを正確に知っている必要が ないからです では プレースホルダに入る型を コンパイラはどう判断するのでしょうか 型推論をパズルだと考えてください 型推論のアルゴリズムは ソースコードの手がかりから― 足りないピースを埋めてパズルを解きます
1つピースを埋めると 残りのピースが見えてきます
ソースコード内の手がかりを使い― コンパイラの型推論と同じように パズルを解いてみましょう まず“smoothies”の引数から “Element”に入るものが分かります “smoothies”は型を持つプロパティであり― QuickHelpで“Smoothie”の 要素の配列だと確認できます そこで“Element”に“Smoothie”が入ります
Elementのプレースホルダの1つを埋めたら― Elementのプレースホルダを すべて“Smoothie”に置き換えます
Elementのプレースホルダを埋めると― FilterKeyの具象型が見えました KeyPathリテラルは“Smoothie.title”を 参照していますね QuickHelpを使うと “Smoothie.title”は文字列だと分かるので― FilterKeyには“String”が入ります FilterKeyが何か分かったので― 他のFilterKeyのプレースホルダも “String”に置き換えます
最後のピースは“RowContent”で― 後端のViewBuilderクロージャの 戻り値の型です
クロージャは このbodyに 1つのビューしか持たないので― “SmoothieRowView”の型を持つ 子ビューを1つ返します ここで“RowContent”が何か分かったので― 最後のプレースホルダを “SmoothieRowView”に置き換えます これでパズルの最後のピースが解けました コンパイラはコード内で このように型推論を行います 皆さんが書いたコードが コンパイラの手がかりです アルゴリズムを進めていくと 次々と情報が見えてきます しかし 手がかりからコンパイラが推論した 具象型のプレースホルダが― 残りのパズルと符号しない可能性もあります 合わないピースがあると パズルは解けません これはソースコードエラーを意味します ではパズルを解く時に ソースコードエラーがあると― 型推論戦略はどう変更するか説明します それから ツールでエラーを見つけて 修正する方法を説明します コンパイラはFilterKeyの具象型を どこで推論するのか― 先ほどの手順を振り返ります KeyPathのベース型は “Smoothie”だと判断し― “Smoothie.title”の型を調べて FilterKeyの具象型を見つけました KeyPathリテラルに 誤ったプロパティを使っていたら― FilterKeyの型を 誤ったプロパティの型だと判断するでしょう この例ではBoolです そして同じ誤った型を FilterKeyのプレースホルダに入れます isIncludedクロージャでの このBool型の使われ方を見れば― BoolはhasSubstringメソッドを 持たないため 違うと分かります コンパイラはエラー箇所を報告します “エラー” 私たちが書くコードにミスは付き物です プログラミング言語とツールは 人間の限界を踏まえて設計されるべきです Swiftコンパイラは型推論アルゴリズムに エラー追跡を統合し― ミスを見つけて通知するよう設計されています 型推論の際に発生したエラー情報を コンパイラは すべて記録し― 型推論を進めるため 発見的探索法でエラーを修正します 型推論が完了すると 収集したエラーをすべて報告します ソースコードのエラーを自動修正するために 実行可能な修正案や― エラーにつながると判断した具象型について ノートを残します Xcode 11.4のSwift 5.2 または Xcode 12のSwift 5.3で― 統合されたエラー追跡は 多数のエラーメッセージを導入しました このコンパイラはエラーメッセージを活用した 新戦略を使用します 誰もがエラーを修正しようとして イライラした経験がありますよね このエラーメッセージは あなたのパートナーになって― ミスの発見と修正を手伝ってくれます エラーがある状態で 先に進ませることはしません ではXcodeでSwiftコードを書く時に エラー修正する方法を見ていきます コードを書く前にXcodeのメニューにある― “Behaviors”の“Edit Behaviors”を開きます ビルド失敗時に 自動でIssue Navigatorが 表示されるBehaviorを追加します これでビルドに失敗したら 必ずエラーが表示されるようになりました SmoothieListの現在の実装とプレビューです 追加したFilteredListが Project Navigatorにありますが― リストを置き換える前に 検索フィールドを追加する必要があります 検索フレーズを保存する Stateプロパティは追加済みです VStackのリストの上部に TextFieldを追加し―
titleを“Search”にして “searchPhrase”に渡します “Command+B”でビルドし ミスがないことを確かめます 今 追加したコードの行に コンパイラエラーが出ました クリックしてエラーを確認します
どうやら 私が使った引数はStringで― TextFieldイニシャライザのパラメータ型の Bindingと互換性がないようです 検索フィールドに渡す値を間違えていました Swiftコンパイラは Bindingの互換性のある型を見つけ― “$”を使うことで 修正できると教えてくれています
次にリストを 新しいFilteredListに置き換えて―
再度 ビルドしてミスがないか確かめます
別のエラーが出ました エラーを開くと“Smoothie”が― FilteredListの要件の“Identifiable”に 準拠していないとのこと コードに“Identifiable”と書いていないのに なぜでしょうか
左側のIssue Navigatorに エラーのコンパイラノートがあります 新しいエラー追跡では 型推論中にエラーを見つけた時の事象を― コンパイラが情報として記録します コンパイラノートという 手がかりを残すことで― コードの違う部分を確認できます これによって Source Editorのエラーと― プロジェクトの別ファイルの 不可欠な情報とが結び付きます Xcode 12のSwift 5.3には さらにノートが増えています ノートをエラーと並べて見たいので― まず“Command+Enter”でキャンバスを閉じます 次に“Option+Shift”を押しながら― ノートをクリックし “Destination Chooser”を表示します カーソルを右へ移動させて“Enter”を押し 新規エディタでファイルを開きます これはFilteredListの宣言です ノートを開くと Elementの具象型は “Smoothie”と推論されていると分かります この宣言を見るとElementまたはSmoothieは “Identifiable”の準拠が必須です FilteredListで試す前に Smoothieを準拠させ忘れていました 左側の“Smoothie”を“Command+クリック”し “Jump to Definition”で移動し― “Identifiable”に準拠させます Smoothieは “id”のプロパティを持つので― “Identifiable”のプロトコルの要件は 実装済みです 念のため再度ビルドして エラーを修正できたことを確認します エラーが なくなりました 右のエディタを閉じ― “Back”を押してSmoothieListに戻ります FilteredListイニシャライザの引数に QuickHelpを使うと― コンパイラが推論した具象型が表示されます “Option+クリック”でKeyPathリテラルに QuickHelpを試します
型推論のパズルを解いた時のように― 推論したKeyPathのベース型を基に titleの型を“String”と判断しました コンパイラは上の行のSmoothieの引数を基に ベース型を“Smoothie”と判断しています
最後にキャンバスで フィルタが成功するか確かめます “Command+Option+Enter”で キャンバスに戻ります
検索バーにテキストが入った プレビュープロバイダの― 別のプレビューも追加します
プレビュープロバイダは テストしたい検索フレーズを反復します この例では 空白の文字列と“Berry”です “Command+Option+P”でプレビューを リフレッシュすると― 成功を確認できました 下のプレビューの検索バーに“Berry”と書かれ 名前に“Berry”が付く スムージーのみ表示されました コードに誤字や入力ミスがあると― コンパイラから実行可能な エラーメッセージが出ます 新しく統合されたエラー追跡で― コンパイラは より多くの エラー情報を集められます そして それをノートとして表示します Xcode 12のSwift 5.3の 改良点のごく一部を紹介しました このセッションでは 再利用可能ビューで インターフェイスを作ると― SwiftUIコードは 型推論に依存していると分かりました 次にソースコードの手がかりから 型推論が どのように考えて― 省略された情報を判断しているかを学びました 最後に 型推論アルゴリズムに エラー追跡を統合することで― コンパイラは 多くの情報を記録し エラーメッセージを残すと学びました エラーの発見と修正に役立ちます 統合されたエラー追跡の詳細は Swiftブログの― “New Diagnostic Architecture”を お読みください SwiftUIビューの構築は― developer.apple.comの SwiftUIチュートリアルが お薦めです Swiftのジェネリクスの詳細については― WWDC2018の“Swift Generics”をご覧ください ありがとうございました
-
-
2:56 - SmoothieList
import SwiftUI struct SmoothieList: View { var smoothies: [Smoothie] @State var searchPhrase = "" var body: some View { FilteredList( smoothies, filterBy: \.title, isIncluded: { title in title.hasSubstring(searchPhrase) } ) { smoothie in SmoothieRowView(smoothie: smoothie) } } } extension String { /// Returns `true` if this string contains the provided substring, /// or if the substring is empty. Otherwise, returns `false`. /// /// - Parameter substring: The substring to search for within /// this string. func hasSubstring(_ substring: String) -> Bool { substring.isEmpty || contains(substring) } }
-
3:53 - FilteredList
import SwiftUI public struct FilteredList<Element, FilterKey, RowContent>: View where Element: Identifiable, RowContent: View { private let data: [Element] private let filterKey: KeyPath<Element, FilterKey> private let isIncluded: (FilterKey) -> Bool private let rowContent: (Element) -> RowContent public init( _ data: [Element], filterBy key: KeyPath<Element, FilterKey>, isIncluded: @escaping (FilterKey) -> Bool, @ViewBuilder rowContent: @escaping (Element) -> RowContent ) { self.data = data self.filterKey = key self.isIncluded = isIncluded self.rowContent = rowContent } public var body: some View { let filteredData = data.filter { isIncluded($0[keyPath: filterKey]) } return List(filteredData, rowContent: rowContent) } }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。