View in English

  • Apple Developer
    • 今すぐ始める

    「今すぐ始める」を詳しく見る

    • 概要
    • 学ぶ
    • Apple Developer Program

    最新情報

    • 最新ニュース
    • Hello Developer
    • プラットフォーム

    プラットフォームを詳しく見る

    • Appleプラットフォーム
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store

    特集

    • デザイン
    • 配信
    • ゲーム
    • アクセサリ
    • Web
    • Home
    • CarPlay
    • テクノロジー

    テクノロジーを詳しく見る

    • 概要
    • Xcode
    • Swift
    • SwiftUI

    特集

    • アクセシビリティ
    • App Intent
    • Apple Intelligence
    • ゲーム
    • 機械学習とAI
    • セキュリティ
    • Xcode Cloud
    • コミュニティ

    コミュニティを詳しく見る

    • 概要
    • 「Appleに相談」イベント
    • コミュニティによるイベント
    • デベロッパフォーラム
    • オープンソース

    特集

    • WWDC
    • Swift Student Challenge
    • デベロッパストーリー
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Center
    • ドキュメント

    ドキュメントを詳しく見る

    • ドキュメントライブラリ
    • テクノロジー概要
    • サンプルコード
    • ヒューマンインターフェイスガイドライン
    • ビデオ

    リリースノート

    • 注目のアップデート
    • iOS
    • iPadOS
    • macOS
    • watchOS
    • visionOS
    • tvOS
    • Xcode
    • ダウンロード

    ダウンロードを詳しく見る

    • すべてのダウンロード
    • オペレーティングシステム
    • アプリ
    • デザインリソース

    特集

    • Xcode
    • TestFlight
    • フォント
    • SF Symbols
    • Icon Composer
    • サポート

    サポートを詳しく見る

    • 概要
    • ヘルプガイド
    • デベロッパフォーラム
    • フィードバックアシスタント
    • お問い合わせ

    特集

    • アカウントヘルプ
    • App Reviewガイドライン
    • App Store Connectヘルプ
    • 近日導入予定の要件
    • 契約およびガイドライン
    • システムステータス
  • クイックリンク

    • イベント
    • ニュース
    • Forum
    • サンプルコード
    • ビデオ
 

ビデオ

メニューを開く メニューを閉じる
  • コレクション
  • すべてのビデオ
  • 利用方法

その他のビデオ

  • 概要
  • トランスクリプト
  • Swiftのジェネリクス(発展版)

    ジェネリクスは、Swiftの最もパワフルな機能の1つです。柔軟かつ再利用可能な要素を記述しながらも、静的な型情報を維持することができます。プロトコルを一般化する方法、関連する型の異なる機能を表現するためにプロトコル継承を使う方法、条件付き適合を使って構成可能なジェネリック要素を作成する方法、クラス継承とジェネリクスの相互関係など、Swiftのジェネリクスについて学んでいきましょう。WWDC 2018では、セッションの内容を発展させ、再帰的な制約に関する新しい情報も提供されます。

    リソース

      • HDビデオ
      • SDビデオ
    • プレゼンテーションスライド(PDF)

    関連ビデオ

    WWDC20

    • Swiftの型推論を利用する

    WWDC18

    • アルゴリズムを理解する
    • Swiftの新機能
  • このビデオを検索

    (音楽)

    (拍手) こんにちは 標準ライブラリ担当のベンです 後ほど ダグも紹介します ではSwiftのジェネリクスについて

    最近のリリースで 追加した新機能は― 条件付き準拠や再帰的な プロトコル制約などです

    Swiftで力を入れてきたのは ジェネリクスの表現力向上です 4.2のリリースは大転換です 描いてきた標準ライブラリを やっと完全に実装できました これはAPIの安定性の実現に 必須でした

    では 改めて客観的視点から ジェネリクスを見てみましょう 本日のトピックです 新旧のジェネリクス機能を含め 全体像を理解してください

    まずジェネリクスの動機に触れます

    次に標準ライブラリの型を例に プロトコル設計について話します

    そしてプロトコル継承を振り返り 条件付き準拠との対話を見ます 最後は クラスとジェネリクスで 締めくくります

    まず ジェネリクスの重要性です インパクトを見る方法の1つは 型といったコレクション設計です

    バッファと呼びますが 配列型に似ています バッファ読み込みのAPIには― 要素のカウントや ポジションへのフェッチがあります では その返り値の型は? もしジェネリクスがなければ 何らかの型の作成が必要になります id型やVoid Starです Swiftでは Anyが あらゆる型の代わりとなります

    バッファの処理も返り値はAnyです しかし 不愉快なユーザ体験となります 使うためにはボックスから その型を取り出さねばなりません

    エラーも起こりやすくなります 文字列バッファのコードに 整数を入れてしまったら?

    使いやすさだけでなく メモリ内の 値の表し方も解決したいですね

    文字列バッファの理想的な表現は 要素が1列に並ぶ 連続したメモリブロックです

    でも 型なしではうまくいきません 対応する型が バッファに 分からないからです それで Anyのような 万能な型を使いますが― 型のボックス化などで オーバーヘッドが生じます

    整数バッファが欲しいだけでも コンパイラに示せません やむを得ず柔軟性を代償にします

    更に Anyは あらゆる型になるので 内部メモリにとって大きい場合は インダイレクションも必要です メモリ中にある値に ポインタ保持も必要です この問題の解決は パフォーマンスにも関わります そこで使う技術が― パラメータ多相です これが Swiftのジェネリクスです

    バッファにより多くの情報を与え 対応する型を表せます これを要素型と呼びます 要素型は汎用パラメータで 故に パラメータ多相です バッファに型を教える コンパイル時引数のようなものです これでバッファはAnyを使い 要素型に参照できます

    しかも 型を取り出す際 変換不要で― 誤った型を代入してしまっても コンパイラが教えてくれます

    これで すべてのバッファ型に 要素型が付きました 付いていない型の宣言は コンパイルエラーになります

    要素型なしのバッファも 宣言できると思うかもしれません その場合はコンパイラが 型推測しているだけです ここでは右のリテラルからです 要素は暗黙で存在しています

    バッファのような型が 対応する型の情報は コンパイルとランタイムに 伝えられます

    つまり 全要素を オーバーヘッドなしで 連続したメモリブロックに 保持できるのです 型が任意に大きくてもです

    コンパイラは常に― バッファが対応する要素型を 直接知っています それで最適化の機会を持てるのです ここでは 整数バッファを 宣言していて― ループの高効率なCPU命令への コンパイルが必要です

    このようなループを―

    定期的に書くならメソッドに 抽出するのもいいでしょう バッファの拡張でユニットテストや 読み込みも楽になります

    しかしコンパイルの問題が起きます すべての要素型が 要約されないからです 要素に必要なケイパビリティを コンパイラに伝えないと― このメソッドが使えません

    簡単な方法は 要素型を Intのような型に制約することです

    そのように拡張すれば あとで汎用化するのも簡単です DoubleやFloatのバッファが 必要になれば― 制約した型を見てください プロトコル準拠を見て― 最も汎用的なプロトコルを 探してください ここでは Numericプロトコルで ゼロ値の要素を作成し それに要素を追加するという 2つの機能が果たせます

    では プロトコル抽出の プロセスについて話します

    バッファ型の汎用化について お話ししましたが 他の汎用的な コードを書く場合は? あらゆるコレクションで 使えるコードは?

    例えば バッファに似た配列型や 対のキー値からなる ディクショナリ型です

    また 汎用的でない データ型や文字列型などもあります

    これらに共通のケイパビリティを キャプチャする― コレクションプロトコルの 簡易版を作成しましょう

    まず これらの具体的な型を 見てきました 今から これらを1つにする プロトコルを考えます この考え方は重要です 具体的な型を考えてから プロトコルで統一します

    これらの型に共通することは? プロトコル設計は いわば契約交渉です

    準拠型と 柔軟性を重視する型との間で 自然と駆け引きが 生まれるからです プロトコルのユーザは 拡張のために シンプルなプロトコルを望みます

    ですから プロトコル設計で 重要なのは― あらゆる準拠型と 様々なユースケースです 要はバランスです

    では コレクションプロトコルの 要素型からです これには関連型を使っています 準拠型で必要な 正しい要素の設定は バッファや配列の場合 Swift 4.2では自動で行われます 汎用パラメータも 要素としたからです 汎用的な引数に 共通の規則に従う 意味ある命名をする利点です

    “T”などと適当に命名すると 別の提示が必要です

    他のデータ型には具体性が要り ディクショナリなら 要素型を キーと値のペアにします

    次はサブスクリプトの 追加についてです もし配列型のような型の プロトコルなら― サブスクリプトの引数に Intを望むかもしれません

    しかし Intを取れば 強い関係が生じます 準拠型は 整数の表すポジションを フェッチしなければなりません 配列型などにはうまくいき― プロトコルユーザにも 理解が簡単です でも やや複雑な ディクショナリについては?

    ディクショナリは 大抵 次の要素を見るロジックを含む― 複雑な内部データ構造を含みます 例えば 何らかの内部バッファです オフセットを格納した インデックス型を使用したり サブスクリプト引数を取り 要素をフェッチしたりするものです しかし ディクショナリの インデックス型は― 不透明型でないといけません オフセットに1を足すと すぐ次の要素にではなく 非初期化部分へ 移動してしまっては困ります インデックスどおりに― コントロールされるべきです それには メソッドを追加し 次のポジションを示す インデックスを与えます

    次のステップは― 開始及び終了インデックスの プロパティです カウントだけでは 終了が分からないからです Intは使わないので―

    コレクションプロトコルに 戻しましょう

    これは インデックス型を取る サブスクリプトと― ポジションを進める方法です インデックス用の型を 提供する型も必要ですが 別の関連型を使います

    準拠型は適切なので 配列やデータは インデックス型にIntを与えます 一方 ディクショナリは カスタム実装を与えます

    では インデックスの汎用化のため カウントに戻りましょう この便利なプロパティを コレクションの拡張として戻します コレクションをまたいで インデックスを進め― カウンタをインクリメントさせます

    でもこの実装に 足りない要件があります Intを移動させたので― インデックス型はもう Equatableではありません しかし終了に達したことを知るには Equatableが必要です

    これを解決するには 前と同じく― Equatableの時だけ適用するよう 拡張を制約します でもしっくりきません

    使いやすいプロトコルが欲しいのに 拡張を書く度に 制約を入れるのは面倒です インデックス比較が 常に必要になるからです プロトコル要件として表す方が いいでしょう

    インデックス関連型の 制約としてです プロトコル制約を行い プロトコル準拠の型すべてに Equatable型を提供させます

    これで拡張の度に 特定しなくて済みます

    これもプロトコルとの 取り決めの例です プロトコルユーザには インデックス比較の必要があり 準拠型は 柔軟性を多く失わず 対応できるかでした

    どちらもかないます Intやデータ 配列は 既にEquatableです Swift 4.2の新しい Equatable準拠自動合成で― インデックス型を Equatableにできます

    次はカスタムポイントを含む カウントの最適化です

    今書いたカウントのバージョンは 全コレクションをまたいで 要素数を計算します しかし 多くのコレクションは もっと速くできます 例えば ディクショナリが 要素数のカウントを保持していれば 自身のカウント実装に 使うことができます ディクショナリのカウントを 呼び出すと得られるのは 元の線形時間ではなく 速い定数時間です

    ただ 最適化の追加に 留意点があります プロトコル要件の実行と オーバーロード追加の違いです 今のところ この新バージョンは 単にオーバーロードです つまり ディクショナリには 改良されたカウントがあります では 汎用アルゴリズム内の 呼び出しは?

    例えば 標準ライブラリの マップを書くとします ご存じない場合 これはとても便利です 要素を変形して 新しい配列に戻してくれるのです

    実装はシンプルです 新しい配列を作成して コレクションに動かし 要素を変形して配列に加えます

    要素を配列に加える際 配列は自動的に大きくなります それに伴い― 内部メモリの再割り当てが 必要になる場合もあります 大きくなる度合いによっては 数回必要になり― 時間もかかります メモリの割り当ては負担ですが―

    ここで最適化のコツがあります 最終的な配列の大きさは― 元のコレクションと まったく同じサイズです 配列に加える前に その分を取っておけば― スピードアップを図れます これには カウントを 呼び出します

    ここでは 汎用的な コンテキストとしてです つまり コレクション型は 完全に汎用的なので― 配列やディクショナリ 何でもあり得ます コードをコンパイルする際の カウントが― より良いものかは分かりません この場合 呼び出されるのは 汎用バージョンで 全コレクションに働き 反復します ディクショナリで マップを呼び出しても 良いバージョンではありません

    カスタマイズされた メソッドやプロパティを 汎用的なコンテキストで 呼び出すには― プロトコル要件として 宣言すればいいのです

    コレクションは― 最適なカウントを 提供できるわけです プロトコル要件として 追加することも納得です ただ たとえそれを要件にしても

    各コレクションでは不要です 拡張を通して 既に1つ提供したからです

    プロトコルに要件を加え 拡張を通しデフォルト実装すること これがカスタマイズポイントです

    メソッドやプロパティの 実装が良くなる可能性を コンパイラに知らせます それで汎用コンテキストで プロトコルを通して 動的に実装を行うのです

    今 ディクショナリで マップを呼び出せば 汎用的な機能でも カウント実装が良くなります

    このようなカスタマイズポイントの 追加で― パワフルに恩恵が得られます クラスや実装継承 メソッドの上書きと同様です また 構造体や列挙型にも使えます

    ただ 最適化できない メソッドもあります カスタマイズポイントは バイナリサイズや― ランタイム動作に 少しは影響します ですから カスタマイズポイントの追加は 最適化が適する時に限ります 例えば 先程のマップ操作です あらゆるコレクションに 実装を良くできる方法がないので 追加の意味はありません 拡張のままで結構です

    さて 作成したコレクション型は 十分な機能を備え― 多くの準拠型と 便利なアルゴリズムがあります しかし型の分類には 時に複数のプロトコルが必要です プロトコル継承です では ダグに代わります (拍手) ありがとう ベン

    プロトコル継承は 初期のSwiftからあります どこでプロトコル継承が必要か このコレクションプロトコルで 考えましょう 良い設計のプロトコルで 一連の準拠型を記述し 興味深い汎用アルゴリズムが 書けます しかし コレクションプロトコル として実装できない― アルゴリズムならあります 例えば プレディケートに合致する 最後の要素を探すには 後ろから前へ進むのが 最良の方法です コレクションプロトコルは それを許しません また コレクション内の要素を シャッフルしたくても 変化が必要で コレクションはそれをしません コレクションプロトコルが 悪いのでなく―

    汎用アルゴリズムの追加に もっと何か必要なのです それがプロトコル継承です これは BidirectionalCollection プロトコルです このプロトコルに 準拠する型はすべて コレクションにも準拠し アルゴリズムを使えます しかしBidirectionalCollectionは 後退する要件を追加します ただ この要件を実装できる コレクションは限られます 例えばSinglyLinkedListは 次に飛ぶだけですよね 後退は効率的ではないので BidirectionalCollectionは あり得ません つまり継承の導入は 準拠型を制限します しかし より興味深い アルゴリズムを実装できます 最後のインデックス後の コードはこうです BidirectionalCollectionの 新要件を使って後退させるだけです

    次は 更に興味深い シャッフルアルゴリズムです Swift 4.2のコレクションに 導入されています このアルゴリズムを見て 導入された要件を確認し プロトコルを 意味のあるよう分類します Fisher-Yatesは古くて シンプルです 最初の要素から インデックスを開始し 次に 別の要素をランダムに選び この2つを入れ替えます 次の反復で 左のインデックスを1つ進め また1つ選んで入れ替えます とてもシンプルです ランダムに選んだ別の要素を 入れ替えて進むだけです 最終的に うまく シャッフルされます コードは 少し複雑ですが 大丈夫です 何かのコレクションに 実装してみましょう このコア演算が例です 最初に コレクションの― 今の位置から最後までで ランダムな数を選びます でもこれは整数で 必要なのはインデックスです そこでoffsetByインデックスを使い 開始インデックスから 選んだ位置までジャンプします 次に必要な操作は 2つの要素の入れ替えです シャッフル実装に必要な 2つの操作があるので― 新しいShuffleCollection プロトコルです

    でもやめてください アンチパターンです あるのはアルゴリズム1つです 要件を見つけ プロトコルに包括しましたが 記述したのはアルゴリズム1つです これを行えば プロトコルが山ほどでき そこには大した意味もなく 何も学べません ここで気付くべきは 明瞭なケイパビリティです ランダムアクセスと変化は― 別々のプロトコルに分類できます RandomAccessCollection プロトコルは― 素早くインデックスを動かし ジャンプを可能にします またUnsafeBufferPointerは ランダムアクセスを可能にしても 変化はさせません それは別のケイパビリティです ここにMutableCollection プロトコルもあります 変化は可能ですが ランダムアクセスは不可能な型です さて 継承階層が分割されますね RandomAccessなどのアクセス側と 変化の側です クライアントは複数のプロトコルを 実装できるので問題ありません シャッフルアルゴリズムに戻ると これは― 自分型でRandomAccessCollectionの 拡張として書けます この型はRandomAccessCollectionと MutableCollectionにも準拠します この両方のケイパビリティを 合わせました

    準拠型と汎用アルゴリズムが 複数ずつある場合― プロトコル階層を成形しがちです これらの階層は大きすぎず 細かすぎないことです 要るのはドメイン内の型を記述する 少数のプロトコルです プロトコル階層のビルドでは― 上に行くにつれプロトコルの要件が 少なくなります それで 要件を実装できる 準拠型が増えます 一方で 階層を下りながら 様々なプロトコルを結合すると より複雑なアルゴリズムが 実装できます 高いケイパビリティが要り 対応する準拠型は減りますがね

    では Swiftの新機能 条件付き準拠に進みます Sliceから始めます 一連のインデックスに サブスクリプトを定義すれば コレクションに Sliceを形成できます Sliceは本質的に コレクションの部分表示です これらはコレクションを スライスしたデフォルト型で Sliceと呼ばれます Sliceは ジェネリックアダプタ型です 基本コレクション型で パラメータ化でき― それ自体がコレクションです 土台のコレクションにできることが Sliceにもできると― 予想しますよね “(where:”などで順方向検索し プレディケートやコレクションに 対応するものを見つけます しかし 逆方向検索では 問題に遭遇します バッファが BidirectionalCollectionでも Sliceもそうだとは限りません

    問題ありません Sliceを拡張し それに準拠させましょう 演算の前に 土台となる 基本コレクションに実装できますが コンパイラが承知しません 基本コレクションは コレクションというだけで 演算の前のインデックスは ありません

    修正できます 要件を拡張に導入し BidirectionalCollectionが 基本型でなければと伝えます これが 条件付き準拠です プロトコルへの準拠を 宣言する拡張と― その準拠が妥当である制約です 条件付き準拠はプロトコル階層に うまく重ねられます 土台の基本型が RandomAccessCollectionなら SliceもRandomAccessCollectionと 表現できるのです 今書いた2つの拡張は― 良いSwiftのスタイルです 拡張してプロトコル準拠させれば その意味が分かります これは条件付き準拠にとって 特に重要です 拡張に様々な要件があるからです

    構成の幅も広がり Slice型は土台の基本コレクション と同じことができます

    次は 条件付き準拠の 別のアプリケーションについて RangeはSwiftの常連で 例えば“.. ダブルや整数の範囲も 形成できます もっとパワフルなRangeは 整数の範囲内の 要素に反復できます intRangeはコレクションに 準拠するからです

    “.. Range型と命名されています 土台のバウンド型に汎用的で ダブルのRangeに 上限と下限だけ格納しています シンプルです Swift 4.2より前は― IntegerRangeという 違う型から得ていました CountableRange型です 構造的にはRange型と同じで 型パラメータ1つと 上限下限のバウンドがあります しかしこれは バウンド型に 要件を追加します バウンドがStrideableで 全要素を列挙する要件です RandomAccessCollectionに CountableRangeを準拠させ― 反復ループを可能にさせるのです 条件付き準拠なら もっとうまくいきます では 基本的なRange型を コレクションに変えます バウンド型には Strideableの要件があります シンプルな準拠の適用ですが 良い型パラメータと合わせると パワフルになります

    今 RandomAccessCollectionに 準拠しようとしていますが BidirectionalCollectionにも コレクションにも触れていません 無条件準拠ならこれでOKです RandomAccessCollectionへの 準拠の宣言は― それが継承する 全プロトコルへの準拠を暗示します この場合 コレクションと BidirectionalCollectionです でも条件付き準拠ならエラーです Sliceの例を思い出せば 様々な階層に対して 異なる制約が必要でした 各コレクションに対してです それでコンパイラは― 条件付き準拠に合う制約を 強制しているのです 今回は 全階層にわたり 同じ制約です 明示的に コレクションと BidirectionalCollectionと書け ここに全準拠があると アサートできます または文体を考慮し 異なる準拠を分けられます

    今 Range型はパワフルで CountableRangeと同等です CountableRangeは 消せますが― 実際 CountableRangeを 使うコードは多いです 汎用的型エイリアスとして 残しましょう

    良い解決です これで 要件を加え Rangeを可算可能にできます コレクションへの変更に 必要な要件ですが 単に土台のRange型の別名です

    繰り返しますが ソース互換性に良い解決です RandomAccessCollectionとして 追加機能を持つRangeに 命名することも 良いことです 実際 他コードの クリーンアップに使えます CountableRangeは Stride機能を持つ Rangeなので拡張できます RandomAccessCollectionに 準拠の場合です

    Swift 4.2に導入したのは 扱う型をシンプルにし― 既存のコア型の構成可能性と 柔軟性を高めるためです

    再帰的制約はプロトコルと 関連型の関係を記述します WWDCでは取り上げていませんが Swiftのジェネリクスで 重要な部分です 見てみましょう 再帰的制約は同じプロトコルを示す プロトコル内の制約です このSubSequenceという関連型は これ自体コレクションですが なぜ必要か? 汎用アルゴリズムを見てみましょう コレクションはソート済みです ここに新しい値を挿入する インデックスを探します 値“11”の sortedInsertionPointを探します ここに“11”を挿入しても ソート済み配列はそのままです これは 二分探索の観点から 実装される機能です 二分探索は 分割統治アルゴリズムで― 各段階で 問題サイズを 大幅に減らす決定を行います 次のステップです 二分探索のために まず中央要素“8”を見て 挿入したい値“11”と比較します “11”は“8”より上なので “8”の後ろのコレクションに 挿入すると分かります 探索範囲が二分されました さらに後半の中央要素“14”と “11”と比較します “11”は“14”より下なので 挿入位置は中央より前です これで残りも二分されました 正しい挿入位置に当たるまで コレクションを二分し続けます

    分割統治アルゴリズムは 高速なのが特長です 二分探索は対数時間を取ります つまり インプットが倍になっても 速度は半減しません この対数アルゴリズムは 1ステップの追加だけで 問題サイズを半減できます コードにしてみましょう 中央要素のインデックス探しに 使うのは― 関数でオフセットした RandomAccessCollectionです 次に 値が中央要素の 前に来るかを確認し 挿入位置が 前半か後半かを見極めます この例では 値は中央要素より上なので 中央より後ろのインデックスから Sliceを取っていきます 次にsortedInsertionPoint(ofを 再帰的に呼びます これが 範囲を絞っていく 分割統治アルゴリズムです さて これを使うには スライシング構文が必要です 全コレクションに可能なので インデックスを整え Sliceを生む 汎用操作を導入してください Sliceアダプタは 全コレクションに働き― 土台のコレクションの要素を 表示するとお話ししましたね それで このアルゴリズムが使え― スライシング構文も 提供できるのです ただ 1つ問題があります このSlice型を嫌う コレクションです それらは独自の操作で 異なる型を生み出します 文字列は最も良い例です スライスすれば 部分文字列が返されます 文字列に分割統治アルゴリズムを 使うなら― それらを部分文字列にも 適用したいですね Rangeも面白い例です スライス操作で返されるのは まったく同じRange型ですが 新しいバウンド付きです そこで 様々な型を キャプチャするために コレクションプロトコルに スライス用の要件を導入します スライスするサブスクリプトを 要件として引き入れると― 結果の型が 新たな関連型 SubSequenceで記述されます

    これで文字列もRangeも 新しい要件を満たします 文字列のSubSequence型は 部分文字列で― RangeのSubSequence型は Rangeです でも この2つ以外の― SubSequence型の カスタマイズを嫌う型には? スライスのデフォルト制限で― これらの型の作者は コレクション準拠の手間が省けます スライス動作は無償です では SubSequenceから始めます 関連型は 自身で デフォルト値を持てます “=”のあとに書かれています SubSequenceにとって Slice アダプタ型は完全なデフォルトです 自身のSubSequence型を持たない すべての準拠型に使えます スライスするサブスクリプトの 実装とも良い相性です “extension”中に書かれています スライスするサブスクリプト操作で デフォルト実装もできます 一歩進んで そのデフォルト実装の 適用制限を行えば― デフォルトのSubSequence型を 選べます つまり 文字列など 独自のSubSequenceを持つ型で デフォルト実装の オーバーロードを防げるのです スライスを無償で使うのも カスタマイズするのも自由です しかし目的は 分割統治アルゴリズムを 書くことでしたね ここで 重要な疑問が出てきます SubSequenceの役割です スライスするサブスクリプト操作の 結果の型というだけでは 実際に使えません では SubSequenceの観点から このアルゴリズムを見ましょう これは再帰的であり SubSequence型の値となった Sliceを形成します 次にsortedInsertionPointを 再帰的に呼びますが 返されたSubSequence型は コレクションでなければなりません 呼び出し時 コレクションの 要素型の値を渡しますが 再帰的呼び出しで期待されるのは このSubSequenceの要素型の値 理にかなう唯一の方法は 要素型が同一であることです インデックスが返される時も 同じ問題が起こります SubSequence側から出されますが 返されるインデックスも 有効でなければなりません コレクションプロトコルの 全要件はキャプチャできます 第1に コレクションの SubSequenceはコレクションです いわゆる再帰的制約です 関連型は そのプロトコルに 準拠するので― SubSequenceのさらなる制約に 関連型“where句”を使えます その要素型は― 元のコレクションの要素型と 同じでなければなりません 故に SubSequence.Elementは Elementと同じ制約です インデックス型もまったく同じです sortedInsertionPoint(of の実装で― これまで見た全プロパティを カバーできます では SubSequenceは スライスできますか? SubSequenceはコレクションで コレクションはスライス可能なので 当然できます 結果は SubSequenceの SubSequenceです 再度行えば SubSequenceの SubSequenceのSubSequence… ずっと続けられます 各段階で真新しい型が作られ 型の“果てしない塔”ができます 問題ありません 再帰の各段階でも 新しい型ができるかもしれません 基礎は現コレクション型です 再帰がランタイムで終了する限り 問題ありません しかし 非再帰的にすれば 分割統治アルゴリズム実装は より効率的です

    sortedInsertionPoint(of の 非再帰的な実装を見てください コアアルゴリズムは同じですが 再帰的ではなく 反復的に表現されています まず コレクション全体の Sliceを取ります このSlice変数は 各反復の コレクションの一部を表します 見慣れた分割統治アルゴリズムです Sliceの中央を探し その中央要素に対して 挿入する値を比較します そして Sliceをスライスし 探索範囲を絞ります ここで問題に遭遇します SubSequence型のSlice変数に 代入していますが 一方 右部分はSliceのSliceです SubSequenceのSubSequenceで まったく異なる型かもしれません 型の相違が考慮され コンパイラエラーになります 非再帰的アルゴリズムが 書けなくなるので不便ですし 特定のコレクション型の動作も 反映されません 例えば 文字列のスライスは 部分文字列ですが そのスライスは 部分部分文字列ではありません 部分文字列型の別インスタンスです このSliceアダプタの動きを 振り返ってみましょう コレクションSelfを “i”から“j”までスライスすると Sliceの型ができます これは土台のSelfの表示に 過ぎません さらにスライスすると Slice>で 同じ土台のコレクションSelfの 表示の表示です これが“果てしない塔”の実例です しかし 他の方法もあります Slice型は 土台のコレクションと 同じインデックスを使いましたね 土台のコレクションは 分かっているので― スライス時に新インデックス “i2”と“j2”が取れます それらを元に戻せば 新しいSliceが形成されます Sliceをスライスすれば 同じSlice型を得られるのです 再帰を効率的に一括できます 先程の部分文字列でも同じです すべてのSubSequence型に 同じことが期待できます では 要件の明示的部分として モデルを作りましょう SubSequenceのSubSequenceは SubSequenceと同じ型です Sliceをスライスすれば 同じSliceが返るので― 非再帰的な分割統治アルゴリズムが 可能になります 型の“果てしない塔”は もう不要です ただ 最後の問題として― SubSequence型は Random AccessCollectionであるべきです offsetByインデックス操作の ためです この記述には where句が使えます BidirectionalCollectionを 継承すると― それに準拠するという制約が SubSequenceに新たに加わります これも再帰的制約ですが このプロトコル上に表されます RandomAccessCollectionでも まったく同じです これのSubSequenceは RandomAccessCollection準拠です SubSequenceの制約が そのプロトコルに従うのは 聞き覚えがあるかもしれません 再帰的制約も条件付き準拠も プロトコル階層を追跡します この機能で SubSequenceに対し 関連型のデフォルトが得られ― SelfのSliceは コレクション階層の どのレベルでも働くのです BidirectionalCollectionを作れば これに準拠するSubSequence型が 必要となります Sliceアダプタの条件付き準拠で BidirectionalCollectionと なります その要件も満たします RandomAccessCollectionも 同様です SubSequenceはその要件を取得し Sliceの条件付き準拠で その要件を満たします それ自体が RandomAccessCollectionです 関連型のデフォルトが 階層内の全プロトコルに働くのは 凝集設計の好例と言えます コレクション階層で 様々な関連型のデフォルトが要ると 設計は困難かもしれません 再帰的制約はパワフルで 関連型とwhere句に使えます 必要なプロトコル要件が書け 分割統治アルゴリズムを 汎用コードで表せます では WWDCの最終パートです Swiftはマルチパラダイム言語で オブジェクト指向プログラミングも サポートしています そこで 2つの機能が― Swift言語でどう対話するかを お話しします まず クラス継承の働きは ご存じですね シンプルです スーパークラスのVehicleや 継承したサブクラスの TaxiやPoliceCarが宣言できます オブジェクト指向階層ができたら サブクラスが どこで使えるか考えます Vehicleを動かす メソッドを使えば サブクラスのTaxiでも 呼び出せると考えます オブジェクト指向プログラミングの 基本です バーバラ・リスコフが 80年代に述べて以来― “リスコフの置換原則”と 呼ばれています シンプルな考え方です プログラム内でスーパー型 つまり― Vehicleのようなスーパークラスを 参照するとします するとTaxiやPoliceCarのような サブ型 つまり― サブクラスのインスタンスが 取れるはずです 型チェックと動作は 正常に継続します ここでの置換は サブクラスと スーパークラス間です シンプルな原理で 取り込み済みですがパワフルです 考えてみてください 考えもしなかった違うサブクラスを 得たらどうなるかをね

    では ジェネリクスに戻ります 置換原則を汎用システムに 適用すると 何ができますか? 例えばDrivableプロトコルを追加し Vehicleを拡張できます ここで期待するのは― そのプロトコル準拠を サブクラスにも使用できることです 汎用アルゴリズムをDrivableに 追加し sundayDriveに行くなら そのAPIをPoliceCarでも 使えるはずです 良い考えかはともかくね

    ここで プロトコル準拠は サブクラスにも継承され― 制約が課されます VehicleをDrivableにする準拠を 1つ書けば― あとから足すものも含め Vehicleの全サブクラスに働きます 大抵の場合 うまくいきます しかし サブクラスに 要件を加える場合もあります よくあるのは イニシャライザの 要件を扱う場合です 例えば Decodableプロトコルの 要件はイニシャライザで― デコーダから 準拠型の インスタンスを作成します

    使うには このプロトコルに 便宜メソッドを加えます これは静的メソッドdecodeで 新しいインスタンスを作成します このメソッドの興味深い点は2つ 第1に― 大文字SのSelfを返すこと 準拠型で 静的メソッドを 呼び出すのと同じ型です

    第2に どのように実装するかです 上のイニシャライザを呼び 適当なDecodable型の 真っ新の インスタンスを作成して返します

    いいでしょう これでVehicle型を Decodableにできます そして置換原則を 適用する際の期待は― Vehicleのサブクラスでも 新規APIが使用できることです つまり Taxiで decodeを呼び出すと VehicleのでなくTaxiの インスタンスが返されるのです

    その仕組みは? TaxiのHourlyRateを 見てみましょう “Taxi.decode(from:”を 呼び出すと イニシャライザの要件を 確認します 実際に呼び出せるのは Vehicleクラス内で宣言された イニシャライザだけです スーパークラスです

    Vehicleのデコード法は 知っていますが Taxiサブクラスについては無知です このイニシャライザを直接使うと HourlyRateは初期化されず― 請求書を受け取る際に 誤解が生じ得ます

    対処方法は? Swiftならこの問題は起こりません Decodableプロトコルに 準拠させる時点で診断し Requiredとマークするからです

    このイニシャライザは 全サブクラスで実装されます 直下のサブクラスに限らず― それらのサブクラスや 将来のサブクラスもです この要件の追加により TaxiにVehicleを継承した際― 同じ名前のイニシャライザの導入も 必要です このイニシャライザは HourlyRateをデコードし― スーパークラスにつなげ Vehicle型もデコードします

    ところで スライドの “non-final”に気付きましたか? 定義では ファイナルクラスは サブクラスを持たず― あとで置換されることはありません

    Requiredイニシャライザも 不要です Decodableなどの要件の扱いも 少し楽になります Requiredイニシャライザが 不要ですからね 参照の意味でクラスを使う場合― クラスのカスタマイズが不要なら ファイナルを使いましょう 後ほど 拡張して カスタマイズは可能です 構造体や列挙型と同じく拡張でき 準拠も追加できます ただ 汎用システムとの 対話を簡略化し コンパイラもランタイムで 最適化しやすくなります

    Swiftのジェネリクスの 背景にある考えは― 静的な型情報を保持しつつ コード再利用することです それで正しいプログラムが書け 実行も効率良くなります プロトコル設計は駆け引きです 汎用アルゴリズムと 準拠型のバランスで 意味のある抽象化を 行っていきましょう 新しい汎用アルゴリズムの実装に 特別なケイパビリティが必要なら プロトコル継承を導入してください 汎用型を書く際は 条件付き準拠の導入です プロトコル階層と動く場合に 特にうまく構成できます クラス継承と汎用システム間の 複雑な対話を考える際は― リスコフの置換原則に戻って 考えてください スーパークラスでなくサブクラスを 導入したらどうなるかと

    以上です アルゴリズムを活用して コード構築に役立てたり Swiftのコレクションを 有効に使うセッションもあります ありがとう (拍手)

Developer Footer

  • ビデオ
  • WWDC18
  • Swiftのジェネリクス(発展版)
  • メニューを開く メニューを閉じる
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    メニューを開く メニューを閉じる
    • アクセシビリティ
    • アクセサリ
    • Apple Intelligence
    • App Extension
    • App Store
    • オーディオとビデオ(英語)
    • 拡張現実
    • デザイン
    • 配信
    • 教育
    • フォント(英語)
    • ゲーム
    • ヘルスケアとフィットネス
    • アプリ内課金
    • ローカリゼーション
    • マップと位置情報
    • 機械学習とAI
    • オープンソース(英語)
    • セキュリティ
    • SafariとWeb(英語)
    メニューを開く メニューを閉じる
    • 英語ドキュメント(完全版)
    • 日本語ドキュメント(一部トピック)
    • チュートリアル
    • ダウンロード
    • フォーラム(英語)
    • ビデオ
    Open Menu Close Menu
    • サポートドキュメント
    • お問い合わせ
    • バグ報告
    • システム状況(英語)
    メニューを開く メニューを閉じる
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles(英語)
    • フィードバックアシスタント
    メニューを開く メニューを閉じる
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program(英語)
    • Mini Apps Partner Program
    • News Partner Program(英語)
    • Video Partner Program(英語)
    • セキュリティ報奨金プログラム(英語)
    • Security Research Device Program(英語)
    Open Menu Close Menu
    • Appleに相談
    • Apple Developer Center
    • App Store Awards(英語)
    • Apple Design Awards
    • Apple Developer Academy(英語)
    • WWDC
    最新ニュースを読む。
    Apple Developerアプリを入手する。
    Copyright © 2026 Apple Inc. All rights reserved.
    利用規約 プライバシーポリシー 契約とガイドライン