iPhone
 Computer >> コンピューター >  >> スマートフォン >> iPhone

SwiftのGenericsとAny型の違いを徹底解説!型安全なコードを書くために知っておきたいポイント

Swiftは、現代において最も型安全なプログラミング言語の一つとして広く認知されています。

そもそも「型安全」とは何か?

型安全(タイプセーフ)な言語とは、常に操作がその時点で利用可能な正しい種類のデータに対して行われることを保証する言語のことです。

具体的には、Int、Float、String、Array、Dictionaryといった異なるデータ型を宣言できる能力を持ち、かつ特定のデータ型で宣言された変数が、決して別の種類のデータを保持しないことを保証できる言語を指します。

型安全な言語では必ず型チェックが行われます。このチェックは言語によって異なり、コンパイル時に行われる場合もあれば、ランタイム時に行われる場合もあります。

SwiftにおけるGenerics(ジェネリクス)とは?

Genericsは、型安全性を維持したまま、柔軟で再利用可能な関数や型を記述するための強力な機能です。Genericsを使えば、あらゆるデータ型に対応するコードを一度書くだけで済みます。Swiftは型安全な言語であるため、その安全性を損なうことなく、コードの重複を避けるジェネリックなコードを実現できます。

簡単な例を挙げましょう。Array(配列)は、同じ型のデータのみを保持できる順序付きコレクションです。そのため、Arrayの定義を見るとElementというジェネリック型を受け取るようになっています。つまり、Arrayはコレクションのジェネリック型だと言えるのです。

では、SwiftのAny型とは何か?

SwiftはAny型もサポートしています。その名の通り、Any型は構造体、クラス、列挙型、関数型など、あらゆる型のインスタンスを表すことができます。

Objective-Cのid型とSwiftのAny型は同じもの?

Swift 3以降、Objective-Cのid型はSwiftのAny型にマッピングされるようになり、SwiftとObjective-Cの相互運用性が大きく向上しました。

どのように、そしてなぜ?

Swift 2の時代には、Objective-Cのid型はSwiftのAnyObject型にマッピングされていました。これは多くの場合うまく機能していましたが、時に予期しない動作を引き起こすことがありました。Swiftの重要な概念の一つである「値型」に対して、このマッピングは適切な扱いをしていなかったのです。

Objective-CはiOS開発で長年使われてきた言語であり、多くの既存プロジェクトはObjective-Cで開発されていました。そこでObjective-CのプロジェクトをSwiftへ移行する際には、「任意のSwift型を任意のObjective-Cオブジェクトにブリッジできること」が要件となりました。

しかし、Int、String、Floatなどの値型やSwiftのクラスには、すでに対応するObjective-Cの型が存在していたため問題ありませんでした。問題になったのは、Objective-Cに対応する型が存在しなかったSwiftの値型です。

この課題を解決するために、Objective-Cのid型はSwiftのAny型へとマッピングされることになったのです。

本題:GenericsとAny型は本当に同じものなのか?

定義の確認はここまでにして、本題に入りましょう。上記の説明だけを見ると、GenericsとAny型は同じように見えるかもしれません。しかし、表面的には似ているものの、実際にはいくつかの重要な違いが存在します。

誰もがデータ構造におけるスタック(Stack)をご存知でしょう。スタックは基本的な線形データ構造で、要素の挿入と削除が一端のみで行われる仕組みです(LIFO:Last In First Out)。

ここからは、このスタック構造をSwiftで実装してみます。まずGenericsを使った実装、次にAny型を使った実装を見ていきましょう。

Genericsを使ったスタック実装

まずはGenericsを使用したスタック実装です。構造体はElementというジェネリック型を受け取り、その型を使ってスタックを実装します。

このコードでは、整数型(Int)の要素を保持できるジェネリックスタックを宣言し、整数要素をプッシュしています。ここまではすべて正常に動作します。

しかし、このgenericStackにFloat型の要素をプッシュしたらどうなるでしょうか?

SwiftのGenericsとAny型の違いを徹底解説!型安全なコードを書くために知っておきたいポイント

❌ コンパイルエラーが発生します!

Any型を使ったスタック実装

続いて、Any型を使用したスタック実装です。こちらではitems配列がAny型の要素を保持でき、定義時にitems配列の要素の正確なデータ型を指定していません。それでは、同じ基本操作を行ってみましょう。

問題ありませんね。こちらも正常に動作しています。最初にスタックを宣言し、2つの整数要素をプッシュしました。show()メソッドを呼び出すと、正確に配列 [3, 4] が出力されます。

次に、Float型の値をプッシュしてみましょう。

SwiftのGenericsとAny型の違いを徹底解説!型安全なコードを書くために知っておきたいポイント

✅ エラーなし!すべて正常に動作します!

裏側では何が起きているのか?なぜエラーにならないのか?

Genericsは基本的にコンパイラに次のように伝えています:

「私はジェネリック型を宣言しました。後で具体的な型を渡します。その型を私が指定した場所すべてに適用してください。」

一方、Any型は基本的にコンパイラに次のように伝えています:

「この変数については心配しないでください。型を適用する必要はありません。私は自由にやらせてもらいます。」

つまり、Genericsは柔軟な関数や型を定義できますが、引数の型は依然としてコンパイラによって厳密にチェックされます。一方、Any型はSwiftの型システムそのものを回避するために使われる仕組みです。

ジェネリックスタックの宣言では、スタックが整数型のみを受け入れるようコンパイラに約束しています。そのため、Float型の要素を挿入しようとすると、それは約束を破る行為となり、コンパイル時エラーがスローされます。コンパイラは常に要素が整数型であることを期待しているのです。

しかしAny型のスタックでは、コンパイル時にもランタイム時にもエラーは発生しません。show()メソッドを呼び出しても、スタックは [3, 4, 5.0] と出力されます。つまり、スタックは整数型とFloat型の両方の値を混在して保持しているのです。Any型のスタックには型の制約がないため、どんな型の値でもプッシュできます。ただし、その代償としてランタイム例外が発生するリスクが伴います。

まとめ

Genericsを使えば、Swiftの型安全性を一切妥協することなく、柔軟な関数、構造体、クラス、プロトコルを記述できます。一方、Any型を使うと、ある意味自分自身がボスになり、ほぼ何でも自由にできてしまいます。しかしその自由には、実行時エラーという重大なリスクが隠れているのです。

日常的な開発では、型安全性を保てるGenericsを積極的に活用し、Any型の使用は本当に必要な場面に限定するのがベストプラクティスと言えるでしょう。

最後までお読みいただきありがとうございました!

  1. ビットコインとイーサリアムの違いを徹底解説!投資前に知っておきたい3つのポイント

    2017年は間違いなく暗号資産(仮想通貨)の年となりました。その後、価格は下落したものの、特に主要通貨への関心は衰えていません。最も人気のある2つの通貨であるビットコインとイーサリアムは、この年、劇的な価格上昇を見せました。ビットコインは1,000ドル未満から約20,000ドル近くまで、イーサリアムは10ドル未満から1,300ドル超まで急騰しています。暗号資産への投資に関心がある方のために、ここではビットコインとイーサリアムの主な違いについてわかりやすく解説します。 1. ビットコインは第1世代、イーサリアムは第2世代 ビットコインは、一般の人々の注目を集めた最初の主要な暗号資産です。ある

  2. Windows 10とWindows 11の違いを徹底比較!アップグレードすべきか判断するポイント

    Microsoft(マイクロソフト)は、数々の新機能と改善点を盛り込んだ最新OS「Windows 11」をリリースしました。一方で、これまで数多くのパッチやアップデートが適用されてきたWindows 10は、新しいWindows 11よりも安定したOSと言えます。Microsoftによると、Windows 10のサポートは2025年以降に終了する予定のため、Windows 11へアップグレードするか、Windows 10を使い続けるかを検討する時間は十分にあります。この記事では、Windows 10とWindows 11の性能・機能・改善点を比較し、新しいOSへ移行すべきか、それともWindo