ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Swiftツアー:Swiftの機能とデザインのご紹介
Swiftプログラム言語の基本的な機能と設計哲学について解説します。データのモデリング、エラー処理、プロトコルの使用、並行コードの記述などを行う方法を説明するとともに、ライブラリ、HTTPサーバ、コマンドラインクライアントを含むSwiftパッケージを作成する方法も取り上げます。Swift初心者の方にも、Swift草創期からのユーザーの方にも、Swiftプログラム言語を最大限に活用するうえで役立つセッションです。
関連する章
- 0:00 - Introduction
- 0:51 - Agenda
- 1:05 - The example
- 1:32 - Value types
- 4:26 - Errors and optionals
- 9:47 - Code organization
- 11:58 - Classes
- 14:06 - Protocols
- 18:33 - Concurrency
- 23:13 - Extensibility
- 26:55 - Wrap up
リソース
- Forum: Programming Languages
- The Swift Programming Language
- Tools used: Ubuntu
- Tools used: Visual Studio Code
- Tools used: Windows
- Value and Reference types
- Wrapping C/C++ Library in Swift
関連ビデオ
WWDC23
WWDC22
WWDC21
-
ダウンロード
こんにちは Allan Shortlidgeです Swiftコンパイラに携わっています 今日は 私のお気に入りの プログラミング言語である Swiftをご紹介します
Swiftは現代的なプログラミング言語です 機能が豊富で パフォーマンスに非常に優れ 安全性も妥協しません 軽量な構文で プログラミングを楽しく行うことができ パワフルな機能により 生産性を大幅に向上させます Swiftは Appleのデバイスで動作する アプリを作成するための 最適な言語として知られていますが その他様々な用途にも有用です クロスプラットフォームの システム言語として Swiftはサーバアプリの作成にも 適しています また 昨年から始まった Embedded Swiftの取り組みにより Swiftはリソースに制約のある環境にも 対応できるようになりました 例えば スマートホームデバイスを 駆動するチップなどです 今日はSwiftのコア機能について 一通り見ていきましょう すべてを網羅したり 特定のトピックを 掘り下げるわけではありません 言語に慣れ親しむこと そして Swiftのユニークな設計原則を学ぶことが 目的です Swiftを紹介しながら 次の素晴らしいソーシャルネットワークの インフラを構築する過程で その機能の デモンストレーションを行っていきます コードは3つのコンポーネントを持つ Swiftパッケージとして構成されます 1つ目は ユーザーをグラフで表現するための データモデルを提供するライブラリです 2つ目は グラフクエリに応答できる HTTPサーバです 最後に サーバにリクエストを送信できる コマンドラインユーティリティを紹介します Swiftの説明を始めるにあたり まず プログラミングの 基本的概念から始めましょう それはデータの表現です Swiftでデータを表現する主な方法は 値型を使うことです コードを使って説明します Swiftでは varキーワードを使って このように 変数を導入できます
では2つ目の変数yを宣言して xの値を代入しましょう ここで xの値を変更したら どうなるでしょうか 出力では yの値はまだ1のままです これは驚くことではないでしょう 整数型はほとんどのプログラミング言語で このように動作します しかしこれは 型が値セマンティクスを 持つということの意味をよく表しています 値型には いくつかの重要な特性があります 値型のインスタンスは状態を共有しないため ある値の変更が 同じ型の他の値に 影響を与えることはありません また 値型はアイデンティティを持ちません これは 2つの値が等しい場合 それらの値が 相互に置き換え可能であることを意味します 整数 ブール値 浮動小数点数などの 基本的な型は ほとんどの言語で値型です しかし Swiftでは値型が 至る所に存在します コードを理解しやすくする もう一つの方法は データの可変性を制御することです 先ほどの例で varキーワードを使って xとyを導入しましたが これらは可変になります しかし varの代わりにletを使うと Swiftはこの値が変更されないことを 保証します
次に グラフ内のユーザーのモデルを導入し より複雑なデータ型を構築しましょう Swiftには 複数の値を 1つの値にまとめる構造体があります Userという名前の構造体を作成します
この構造体には いくつかのプロパティが必要です ユーザー名用のもの ユーザーが可視かどうかを制御するもの そして最後に 友達のリストです このリストはユーザー名の文字列の 配列で表現します まずAliceという名前のユーザーを作り その友達を何人か設定します
Aliceを特定の型で宣言しなかった ことに注目してください Swiftは型安全な言語で すべての変数がコンパイル時に 型を持ちますが ここでは型がUserであると推論できるため 記述する必要はありません 次に Brunoという名前のユーザーを作り Aliceの友達を彼の友達として設定します そこで Aliceに新しい友達を追加すると どうなるでしょうか
出力結果を見るとAliceとBrunoの友達は 異なっていることが分かります これは Swiftでは 配列が値型だからです Aliceの友達をBrunoの友達に割り当てると 配列がコピーされました User構造体は 値型で構成されているため それ自体も自動的に値型となります 通常のSwiftコードで目にする型の ほとんどが値型です クラスのような参照型も存在し それについては後で説明しますが その用途はより専門的です Swiftは値型と不変性を重視しています 値がいつ変更できるかを制御することで コードが理解しやすくなるからです 特に並行プログラミングのような 難しい領域で効果的です 次に エラーについてお話しします エラーは日常的なプログラミングの一部です ディスクが一杯になったり ネットワーク接続が失敗したり 不正なデータが入力されたりするからです それでも プログラムは常に動作を続け 問題が発生したら ユーザーに通知しなければなりません Swiftはエラーを報告し スムーズに処理するための エラーハンドリングモデルを提供しています Swiftのエラーハンドリングの哲学は 3つのポイントに要約できます 第1に エラーの原因となる 可能性のあるコード部を 後で驚かないよう マークしておく必要があるということです 第2に エラーには対策を講じられるだけの コンテキストを持たせる必要があります そして最後に Swiftは回復可能なエラーと プログラマーのミスを区別します ネットワーク接続が失敗しても プログラムは動作を続ける必要があります 一方 配列の範囲外アクセスは おそらくコードが間違っていることを 示しています Swiftはプログラムを停止して そのバグが セキュリティ問題に 発展するのを防ぎます
エラー条件をチェックすることでUserモデルを 安全に保つ方法を見てみましょう ここでは friends配列は直接変更できるため ユーザーが自分自身と友達になったり friendsリストに重複する値が 含まれたりなどの 無効な状態が 発生する可能性があります ここでは addFriendというメソッドを作成し 配列への追加を 検証できるようにします
デフォルトでは 構造体のメソッドは 構造体のプロパティを変更できません このメソッドはfriendsを変更できるよう mutatingとして宣言する必要があります ここでは friendsプロパティの 直接使用を防ぎたいので privateセッターを追加し 代わりにaddFriendを 呼び出すようにします
次に addFriendから エラーのレポートを行いたいので それらのエラーを表現する方法が必要です Swiftのenumは優れたエラータイプです
enumは異なるケースの選択を表し 可能性のある あらゆるエラーの原因を列挙します 必要なのは このenumを Errorプロトコルに準拠させることだけです プロトコルについては 後ほど詳しく説明します 次に addFriendの入力が 無効かどうかをチェックし 無効ならエラーをスローします コンパイラはメソッドが エラーの原因なので スローとしてマークする必要があることを 教えてくれています
入力をif文で チェックすることもできますが Swiftのguard文は エラー条件の検出に最適です guard文は 条件が満たされない場合 関数から戻ることを要求するからです ここではaddFriendがエラーを スローしており 処理されていないエラー原因が あることを示す 診断が出ています エラーが発生する 可能性があることを示すため 呼び出しに tryキーワードを付ける必要があります エラーがどのように見えるか 確認したいので エラーをトリガし do/catchブロックで その呼び出しをラップして観察します
これでduplicateFriendエラーが スローされました しかし このエラーには問題があります どの友達が 重複しているかがわかりません そのコンテキストを追加するには このケースに関連値を持たせます
今度はユーザー名が エラーと一緒に表示されます 次に ユーザー名を指定してユーザーを見つける クエリのプロトタイプを作ります ユーザーを保存する場所が必要なので ユーザー名をユーザー構造体に マッピングする辞書を作成しました クエリ関数には ユーザーが見つからない 場合の処理方法が必要です エラーをスローしてもよいですが もう一つの選択肢は 戻り値をオプショナル型にすることです Swiftにはオプショナル値に対する 様々なビルトインサポートがあります オプショナル値は nilか オプショナル型がラップする 任意の型の有効値のいずれかです オプショナルに格納されている値を 利用するには アンラップする必要があります nilと非nilの両方の値をコードで 処理するよう要求することで Swiftは予期せぬnil値が プログラムのクラッシュを引き起こすという 他の言語でよく生じるエラ-を防ぎます オプショナル型が コードで どのように使われるか見てみましょう findUser関数では この辞書での検索結果を 直接返すことができます 辞書の添字演算子は オプショナル型のUserを返すため ここでエラーが発生しています これは キーに対応する値が存在しない 可能性があるため 理にかなっています 疑問符を追加して 関数の戻り値の型を オプショナル型に更新します findUserを呼び出してみましょう
オプショナル型を返すので 結果をアンラップして Userを操作する必要があります Swiftでオプショナル型をアンラップする 最も一般的な方法の一つは if let構文を使用することです nilでない値がある場合 その値はこのif文の本体で letにバインドされます 実行時に常に有効な値があることが 100%確信できる場合は 感嘆符を使用して オプショナル型を 強制的に アンラップすることもできます
Swiftでは実行時に 仮定が正しいかどうかがチェックされます
おっと! 辞書にdashという名前のユーザーが いなかったためプログラムが停止しました 次回はもっと気をつけます Swiftでのエラー処理と オプショナル型の アンラップには共通点があります どちらの場合も コードは必ず あらゆる可能性を処理するように 構成されます プログラム内でエラーに遭遇する 可能性があるそれぞれの場所で エラーをキャッチするか プロパゲートする必要があり throwsおよびtryキーワードでは それがどこで発生するかが正確に示されます オプショナル値を扱う場合は 使用する前に値をアンラップして その存在を確認する必要があります Swiftでは エラーとオプショナル型の 設計によって 正確かつデバッグ可能な プログラムを簡単に書くことができます ソーシャルグラフの基本的なデータモデルの 概要を説明しました ここからは コードに構造を加えていきましょう Swiftがサポートするコード編成の2つの 単位は モジュールとパッケージです Swiftのモジュールは 常に一緒にビルドされるソースファイルの コレクションで構成されます モジュールは他のモジュールに 依存することもできます 例えば アプリを表すモジュールは アプリとサーバの両方で 必要なコア機能を提供する ライブラリモジュールに 依存する場合があります モジュールのコレクションは パッケージとして配布できます 最後に 1つの パッケージ内のモジュールは 別のパッケージのモジュールと 依存関係を持つこともあります Swift Package Managerは パッケージを管理するためのツールで コマンドラインから呼び出して コードをビルド テスト 実行できます SwiftパッケージはXcodeや VS Codeを使って作業することもできます 例えばHTTPサーバの作成などという 特定のタスクを達成するための ライブラリを探しているなら Swift Package Indexで それを実現する オープンソースのSwiftパッケージを 見つけることができます 後で いくつかの オープンソースパッケージを使って サーバとコマンドライン ユーティリティを構築していきます コードに戻りましょう ここまでに記述したものを パッケージに再構成しました パッケージ内の最初のモジュールは ソーシャルグラフデータモデルを含む ライブラリモジュールです ライブラリにはテストも付属しています 以前に定義した エラーenumとUser構造体は それぞれ独自のファイルに移動しました User構造体を確認してみましょう いくつかの変更を加えたことが わかると思います 構造体とその多くのメンバーには public修飾子が付いているため ライブラリ外部のコードから 扱えるようになっています publicは Swiftで利用できる 複数のアクセスコントロールレベルの1つです 他にprivate、internal packageのレベルがあります privateとマークされた宣言には 同じファイル内のコードからのみ アクセスできます internal宣言は 同じモジュール内の他のコードからのみ アクセスできます Swiftでは アクセスレベルを指定しない場合 常に暗黙的にinternalが使用されます package宣言は 同じパッケージ内の 他のモジュールからアクセスできます そしてpublic宣言も 他のあらゆる モジュールからアクセス可能です ここまで値型の話だけをしてきましたが 場合によっては 共有のミュータブルステートを 表現する必要があります そのため Swiftにはクラスなどの 参照型があります 後で ソーシャルグラフデータモデルを 保存するHTTPサーバを構築します そのサーバは 友達の追加や 友達リストの作成などという アクションを実行する リクエストに応答する必要があります リクエストが届いたら それを処理するコードでは ユーザーのコレクションにアクセスするため 抽象化を使用する必要があります そのコレクションは複数のリクエストで 共有/変更される必要があるので 参照型を使って カプセル化しなければなりません それでは より簡単な例を使って クラスを詳しく見ていきましょう オブジェクト指向言語で プログラミングをしたことがあれば Swiftのクラスは 馴染みがあるでしょう クラスは単一継承をサポートしています この例では Catクラスが そのsuperclass Petから継承されています superclassのメソッドは subclassの メソッドでオーバーライドできます そして subclassから superclassへの型変換や その逆も想定通りに動作します Swiftはメモリを自動的に管理します 参照型については Swiftには自動参照カウントと 呼ばれる機能があります バックグラウンドではコンパイラが オブジェクトに対する参照がある限り そのオブジェクトが存在し続けることを 保証します これは参照カウントを増減することで 実現しています 参照がなくなると オブジェクトは直ちに解放されます 自動参照カウントは予測可能であり パフォーマンスに優れています しかし 循環参照が発生すると オブジェクトが 解放されないという問題があります こちらに Pet配列を持つ Ownerクラスがあります PetクラスにはOwnerへの戻り参照があり これが循環参照を作り出しています この循環を解消するために 弱い参照を使ってOwnerの参照カウントの 増加を避けることができます ownerプロパティを弱い参照にすると オプショナル型になります Ownerインスタンスが Petより先に解放されると このプロパティはnilになります 先ほど Swiftでは値型が 重視されると言いましたが クラスも重要な役割を担っています 変更可能な状態を共有したい場合や アイデンティティを持つ オブジェクトや継承が必要な場合 そのジョブには クラスが適しています 多くのオブジェクト指向言語では 継承がポリモーフィズムの 主なメカニズムになっています しかしSwiftでは プロトコルによる より一般的な方法で抽象化を構築できます そしてその方法は 値型と参照型の両方で 問題なく機能します プロトコルとは 型の抽象的な 要件を集めたものです すべての要件を実装することで 型がプロトコルに 準拠していると宣言できます この例では StringIdentifiableは 1つの要件を持つプロトコルです User型はプロパティ識別子の実装を Extensionで提供することで StringIdentifiableに準拠しています Extensionは私のお気に入りの Swift機能の1つです Extensionを使うと メソッド プロパティ プロトコルの準拠を あらゆる型に追加できます その型がどこで 定義されているかは問いません Swift標準ライブラリのコレクション型は プロトコルを使用して抽象化できる 型のファミリーのよい例です Swiftのコレクション型である ArrayやDictionaryを見てきましたが 他にもあります Setは ユニークな要素の 順序なしリストを表す 別の種類のコレクションです 文字列もSwiftではコレクションです Unicode文字のリストを含んでいるからです Collectionに準拠しているすべての型は いくつかの共通の機能を備えています 例えば forループを使用して Collectionに準拠している型の内容を イテレートすることができます また インデックスを使って コレクションの要素にアクセスできます Swift標準ライブラリには 任意のCollectionで使用できる 様々な標準アルゴリズムの実装が 付属しています map filter reduceのような アルゴリズムは 関数型プログラミングを扱ったことが ある方には馴染みがあるでしょう Swiftにはクロージャの 特別な省略記法もあり それを使えば特にこれらのアルゴリズムを すっきりと使用できます パラメータを明示的に 名付けないクロージャでは ドル記号で始まる変数が 匿名でパラメータを表すため 簡潔なコードが記述しやすくなっています では Swiftのコレクション アルゴリズムを使ってみましょう 私のサーバには 友達の友達を表示することで 知り合いかもしれない人を 見つけ出す機能を持たせる予定です コレクションアルゴリズムを使うと このユーザーの集合を計算できます こちらがUserStoreクラスです このクラスは ユーザーの辞書をカプセル化しており ユーザー名でユーザーを検索する いくつかの既存メソッドも持っています ここに friendsOfFriendsをクエリする メソッドを追加します まず 元のユーザーを 検索する必要があります 次に 元のユーザーと その友達のユーザー名を含む Setを作成します このSetには 結果から除外したい ユーザー名が含まれています 次に 関数型プログラミングを使って このメソッドの結果を構築します まず 友達のユーザー名を User構造体の インスタンスにマッピングして その友達にアクセスできるようにします compactMapを使って それぞれの友達のUser構造体を検索し nilであるUserを削除します 次に そのユーザーの友達の 友達をすべて集めます
flatMapはこれらの友達リストを 新しい配列に連結します 最後に 元のユーザーとその友達を 除外する必要があります filterは除外するセットに含まれている ユーザー名を削除します これでほぼ完了ですが この結果には1つ問題があります 重複したユーザー名が 含まれている可能性があるのです 標準ライブラリには 結果を取得してユニークな要素のみを返す アルゴリズムはありませんが ジェネリクスを使うと それを自分で簡単に実装できます Collectionを拡張して uniquedというメソッドを追加します uniquedの実装はシンプルです まず コレクションの内容を含むSetを作成し そのSetを変換して配列に戻します これだけではうまくいきません Set型は 格納される要素が Hashableプロトコルに 準拠していることを要求するからです これは当然です Setは値を効率的に格納するため ハッシュ化に依存しているからです この要件を満たすため ExtensionをCollectionに制限し 要素がHashableに準拠している コレクションだけにします これでuniquedを 呼び出せば完了です
わずか数行のコードで 任意のCollection型に適用できる 便利なアルゴリズムを構築できました Swiftプロトコルの柔軟性のおかげで uniquedメソッドを持つ型のセットは 特定のクラスの階層に限定されません プロトコルとジェネリクスを使えば さらに多くのことができます 興味があれば 「Embrace Swift generics」と 「Design protocol interfaces in Swift」を ご視聴ください HTTPサーバの構築に進む前に Swiftのもう1つの重要な概念である 並行処理についてお話ししたいと思います Swiftの並行処理の基本単位はTaskです これは独立した 並行実行コンテキストを表します Taskは軽量なので 多くのTaskを作成できます Taskの完了を待って その結果を取得することも 不要になったTaskを キャンセルすることもできます Taskは並行して実行できるため サーバが受け取るHTTPリクエストの 処理に最適です Taskの実行時には 非同期操作が行われることがあります ディスクからの読み取りや 別のサービスへのメッセージ送信と 応答待ちなどです Taskは非同期操作の完了を待つ間 一時停止して CPUを他の作業中のTaskに譲ります Swiftではasync/await構文を使用して コードでのTaskの一時停止を モデル化します 一時停止する可能性のある関数は asyncキーワードでマークされます async関数が呼び出されると awaitキーワードを使用して その行で一時停止が発生する 可能性があることが示されます Swiftの並行処理機能を サーバで活用してみましょう 以前に取り組んでいた パッケージの開発を続けるため サーバ開発環境のVSCodeを使用します サーバ用の新しいターゲットを作成して パッケージを更新しました これはオープンソースの HTTPサーバフレームワークである Hummingbirdに依存しています Hummingbirdはリクエストのリスニングと レスポンスの送信を処理してくれるので 自分はアプリのロジックに 集中することができます 接続のリスニングを開始するため 最小限のコードを記述しましたが まだリクエストを行うことはできません リクエストハンドラには UserStoreへの参照が必要です 便宜上 リクエスト間で共有するために UserStoreを拡張して 静的インスタンスを追加します このコードには問題があるようです グローバルなUserStore変数にアクセスすると データ競合が発生する可能性があります UserStoreはSendableではないからです その意味を掘り下げていきましょう サーバが同時に 2つのリクエストを受け取ったとします 最初のリクエストに関連付けられたタスクは ユーザーを検索しなければならず もう一方のタスクは 新しいユーザーを作成している最中です UserStoreは 共有される変更可能な状態であるため これらのタスクは異なるスレッドで 同じメモリにアクセスする可能性があります これはデータ競合であり クラッシュや 予測不能な動作を 引き起こす可能性があります コードでデータ競合を 回避することは重要です そのため Swift 6言語モードでは プログラムのデータ競合の安全性が コンパイル時に完全に検証されます データ競合の安全性を確保するには 1つの方法として 並行処理ドメイン間で共有される値が Sendableであることを要求します Sendableな値とは 並行アクセスからその状態を保護する値です 例えば ある型がSendableと認定されるのは 変更可能な状態を読み書きする際に ロックを取得する場合です UserStoreグローバル変数が 安全でないとコンパイラが通知したのは UserStoreがSendableでないことを 認識したためです UserStoreをSendableにするため 手動で同期を追加することもできますが Swiftには アクターと呼ばれる より便利な機能があります アクターはクラスに似ており 参照型で 共有される変更可能な状態を カプセル化できます しかしアクターは アクセスを直列化することで 状態を自動的に保護します アクターでは 1つのTaskのみ 同時に実行することを許可されます アクターのコンテキスト外からのアクター メソッドの呼び出しは非同期になります これで このエラーが何を意味するか 少し理解できたと思います UserStoreをアクターにすることで 並行アクセスを安全にすることができます
これでアクセスが同期され エラーはなくなりました 次に HTTPリクエストハンドラの 作成に移りましょう friendsOfFriendsルートを追加します これはユーザー名を引数に取り 文字列の配列を返すものです ハンドラはHummingbirdが 独立したTaskで実行するクロージャです UserStoreはアクターであり そこにはこのクロージャ内の異なる 並行処理ドメインからアクセスするので アクセスは非同期になり awaitキーワードの使用が求められます
では サーバにcurlでリクエストを送信して ハンドラを簡単にテストしてみましょう
想定通りのレスポンスが返ってきました Swift 6の完全なデータ競合保護のおかげで サーバが正しく並行処理を行っていると 自信を持つことができます ここではSwiftでの並行コード作成の 基本事項として Taskや async/await アクターなどを説明して きましたが 他にもまだまだあります 出発点として「Explore structured concurrency in Swift」をご参照ください
Swiftの機能に関する ここでの最終カテゴリは 言語の拡張に関するものです このパワフルな機能は多くの場合 ライブラリの作者が表現力豊かで型安全な APIを構築し アプリの定型コードを 排除するために使われます 最初の機能はプロパティラッパーです これらのラッパー型は 格納された値を 管理するロジックをカプセル化します プロパティの読み書きの呼び出しを インターセプトすることで 簡単な注釈でプロパティに適用できる 再利用可能なロジックが 実装されます この例では swift-argument-parserパッケージの Argumentプロパティラッパーが usernameプロパティに適用されています Argumentラッパーは プロパティがコマンドライン引数の 値を格納することを示します これを実際に見てみましょう コマンドラインユーティリティのため パッケージに新規ターゲットを作成しました 新しいターゲットは ArgumentParserパッケージに依存しており これをコマンドライン引数の 解析に使用します メインファイルには AsyncParsableCommandに 準拠する型があり ツールが受け入れる引数の トップレベル構成を提供します
これを実際に見てみましょう
Argument parserにより 私のツールの 整形済みのヘルプメッセージが生成されました しかし現在のところ ツールではただ この説明が表示されるだけです これを変更し 最初のサブコマンドを追加してみましょう 新しい構造体を作成することから始めます これはAsyncParsableCommandに 準拠しています このコマンドは ユーザーのために 友達の友達をリクエストするものです これは先ほど構築した サーバルートを使用します これをサブコマンドとして 登録する必要があります このコマンドは1つの引数 つまりユーザー名を取り Argumentプロパティラッパーで そのプロパティに注釈を付けています 次に runメソッドの実装を 埋める必要があります このツールが送信するHTTPリクエストを カプセル化するために作成した Requestユーティリティを利用します これはコマンドの相対パス レスポンスで期待されるデータの種類 そして辞書形式のいくつかの引数で 初期化されます これはHTTPのget操作なので getメソッドを呼び出すことで リクエストを実行します このメソッドがネットワークリクエストを 送信し レスポンスを待つ間 現在のTaskは一時停止されるため このメソッドは非同期になります それでは実行してみましょう
ユーザー名の指定を忘れたようです Argument parserは自動的に 不足している引数について説明した 有益な出力を生成しています もう一度実行して Aliceを指定した場合の レスポンスを確認しましょう
素晴らしい コマンドは正常に動作しています Argumentプロパティラッパーのおかげで ツールのコマンド作成が非常に 簡単になりました これはSwiftで作成できる 表現力豊かなAPIの良い例です ライブラリデベロッパが活用できる もう一つの言語ツールは Result Buildersです Result Buildersを使用すると 複雑な値を宣言的に表現できます Result Builder APIはクロージャを受け取り その中で特別な軽量構文を使って 結果の値を段階的に構築します この機能はネイティブUIフレームワークや ウェブページジェネレータなどの 構築に使用されています Swift標準ライブラリでは Regex Builderがこの機能を利用して 簡潔な表現の代わりに使用できる 読みやすい正規表現の 文字列を提供しています プロパティラッパーと Result Buildersに加えて マクロも非常に柔軟なツールです マクロはコンパイラプラグインとして 機能するSwiftコードで 構文ツリーを入力として受け取り 変換されたコードを出力として返します Result Buildersについて さらに詳しくは 「Write a DSL in Swift using result builders」をご覧ください また マクロについては「Expand on Swift macros」をご覧ください これでSwiftの 簡単な説明を終わりにします この言語を初めて使う人も すでにしばらく使っている人も ここで学んだことを活用して Swiftのユニークな機能を使って 楽しみながら 素晴らしいものを 作成してもらえれば幸いです コードを書く際は 最適なツールを選ぶ機会を 探してみてください 例えばクラスではなく値型で モデル化を行ったり ジェネリクスを使って アルゴリズムを完全に一般化したり アクターを使って データ競合を解消したりするのです Swiftには洗練されたパワフルなコードを 書くためのツールがすべて揃っています ご覧いただきありがとうございました WWDCを存分にお楽しみください
-
-
1:49 - Integer variables
var x: Int = 1 var y: Int = x x = 42 y
-
3:04 - User struct
struct User { let username: String var isVisible: Bool = true var friends: [String] = [] } var alice = User(username: "alice") alice.friends = ["charlie"] var bruno = User(username: "bruno") bruno.friends = alice.friends alice.friends.append("dash") bruno.friends
-
3:05 - User struct error handling
struct User { let username: String var isVisible: Bool = true var friends: [String] = [] mutating func addFriend(username: String) throws { guard username != self.username else { throw SocialError.befriendingSelf } guard !friends.contains(username) else { throw SocialError.duplicateFriend(username: username) } friends.append(username) } } enum SocialError: Error { case befriendingSelf case duplicateFriend(username: String) } var alice = User(username: "alice") do { try alice.addFriend(username: "charlie") try alice.addFriend(username: "charlie") } catch { error } var allUsers = [ "alice": alice ] func findUser(_ username: String) -> User? { allUsers[username] } if let charlie = findUser("charlie") { print("Found \(charlie)") } else { print("charlie not found") } let dash = findUser("dash")!
-
11:01 - SocialGraph package manifest
// swift-tools-version: 6.0 import PackageDescription let package = Package( name: "SocialGraph", products: [ .library( name: "SocialGraph", targets: ["SocialGraph"]), ], dependencies: [ .package(url: "https://github.com/apple/swift-testing.git", branch: "main"), ], targets: [ .target( name: "SocialGraph"), .testTarget( name: "SocialGraphTests", dependencies: [ "SocialGraph", .product(name: "Testing", package: "swift-testing"), ]), ] )
-
11:12 - User struct
/// Represents a user in the social graph. public struct User: Equatable, Hashable { /// The user's username, which must be unique in the service. public let username: String /// Whether or not the user should be considered visible /// when performing queries. public var isVisible: Bool /// The usernames of the user's friends. public private(set) var friends: [String] public init( username: String, isVisible: Bool = true, friends: [String] = [] ) { self.username = username self.isVisible = isVisible self.friends = friends } /// Adds a username to the user's list of friends. Throws an /// error if the username cannot be added. public mutating func addFriend(username: String) throws { guard username != self.username else { throw SocialError.befriendingSelf } guard !friends.contains(username) else { throw SocialError.alreadyFriend(username: username) } friends.append(username) } }
-
12:36 - Classes
class Pet { func speak() {} } class Cat: Pet { override func speak() { print("meow") } func purr() { print("purr") } } let pet: Pet = Cat() pet.speak() if let cat = pet as? Cat { cat.purr() }
-
12:59 - Automatic reference counting
class Pet { var toy: Toy? } class Toy {} let toy = Toy() let pets = [Pet()] // Give toy to pets for pet in pets { pet.toy = toy } // Take toy from pets for pet in pets { pet.toy = nil }
-
13:26 - Reference cycles
class Pet { weak var owner: Owner? } class Owner { var pets: [Pet] }
-
14:20 - Protocols
protocol StringIdentifiable { var identifier: String { get } } extension User: StringIdentifiable { var identifier: String { username } }
-
15:21 - Common capabilities of Collections
let string = "🥚🐣🐥🐓" for char in string { print(char) } // => "🥚" "🐣" "🐥" "🐓" print(string[string.startIndex]) // => "🥚"
-
15:31 - Collection algorithms
let numbers = [1, 4, 7, 10, 13] let numberStrings = numbers.map { number in String(number) } // => ["1", "4", "7", "10", "13"] let primeNumbers = numbers.filter { number in number.isPrime } // => [1, 7, 13] let sum = numbers.reduce(0) { partial, number in partial + number } // => 35
-
15:45 - Collection algorithms with anonymous parameters
let numbers = [1, 4, 7, 10, 13] let numberStrings = numbers.map { String($0) } // => ["1", "4", "7", "10", "13"] let primeNumbers = numbers.filter { $0.isPrime } // => [1, 7, 13] let sum = numbers.reduce(0) { $0 + $1 } // => 35
-
16:13 - Friends of friends algorithm
/// An in-memory store for users of the service. public class UserStore { var allUsers: [String: User] = [:] } extension UserStore { /// If the username maps to a User and that user is visible, /// returns the User. Returns nil otherwise. public func lookUpUser(_ username: String) -> User? { guard let user = allUsers[username], user.isVisible else { return nil } return user } /// If the username maps to a User and that user is visible, /// returns the User. Otherwise, throws an error. public func user(for username: String) throws -> User { guard let user = lookUpUser(username) else { throw SocialError.userNotFound(username: username) } return user } public func friendsOfFriends(_ username: String) throws -> [String] { let user = try user(for: username) let excluded = Set(user.friends + [username]) return user.friends .compactMap { lookUpUser($0) } // [String] -> [User] .flatMap { $0.friends } // [User] -> [String] .filter { !excluded.contains($0) } // drop excluded .uniqued() } } extension Collection where Element: Hashable { func uniqued() -> [Element] { let unique = Set(self) return Array(unique) } }
-
19:23 - async/await
/// Makes a network request to download an image. func fetchUserAvatar(for username: String) async -> Image { // ... } let avatar = await fetchUserAvatar(for: "alice")
-
19:43 - Server
import Hummingbird import SocialGraph let router = Router() extension UserStore { static let shared = UserStore.makeSampleStore() } let app = Application( router: router, configuration: .init(address: .hostname("127.0.0.1", port: 8080)) ) print("Starting server...") try await app.runService()
-
20:20 - Data race example
// Look up user let user = allUsers[username] // Store new user allUsers[username] = user // UserStore var allUsers: [String: User]
-
22:24 - Server with friendsOfFriends route
import Hummingbird import SocialGraph let router = Router() extension UserStore { static let shared = UserStore.makeSampleStore() } router.get("friendsOfFriends") { request, context -> [String] in let username = try request.queryArgument(for: "username") return try await UserStore.shared.friendsOfFriends(username) } let app = Application( router: router, configuration: .init(address: .hostname("127.0.0.1", port: 8080)) ) print("Starting server...") try await app.runService()
-
23:27 - Property wrappers
struct FriendsOfFriends: AsyncParsableCommand { @Argument var username: String mutating func run() async throws { // ... } }
-
23:57 - SocialGraph command line client
import ArgumentParser import SocialGraph @main struct SocialGraphClient: AsyncParsableCommand { static let configuration = CommandConfiguration( abstract: "A utility for querying the social graph", subcommands: [ FriendsOfFriends.self, ]) } struct FriendsOfFriends: AsyncParsableCommand { @Argument(help: "The username to look up friends of friends for") var username: String func run() async throws { var request = Request(command: "friendsOfFriends", returning: [String].self) request.arguments = ["username" : username] let result = try await request.get() print(result) } }
-
26:07 - Result builders
import RegexBuilder let dollarValueRegex = Regex { // Equivalent to "\$[0-9]+\.[0-9][0-9]" "$" OneOrMore(.digit) "." Repeat(.digit, count: 2) }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。