Swift言語において、型キャスティングのメカニズムは、値をある型から別の型に変換する必要がある場合に、実行時にそのチェックと変換を行うことを目的としています。このメカニズムは静的型付けと継承に基づいており、Swiftでは値型(struct/enum)と参照型(class/protocol)を同時に扱うことができます。問題は、オブジェクトや値がAny型またはプロトコル型の変数に入る場合で、その場合に他の(通常はより特定の)型に変換できるかどうかを確認する必要があります。
安全な型キャスティングは、型安全性、動的ポリモーフィズムの利点を活用し、混合コレクションやクラス階層を操作する際のエラーを防ぐことができます。
Swiftは、3つの形式の型キャスティングを提供しています。
as — 安全なキャスティング(アップキャスト)、コンパイラは結果を事前に認識しています。as? — 条件付き(オプショナル)キャスティング(ダウンキャスト)、キャストに失敗した場合はオプショナルを返します。as! — 強制キャスティング、キャストできない場合は実行時エラーを引き起こします。コード例:
class Animal {} class Dog: Animal { func bark() { print("Woof!") } } let animals: [Animal] = [Dog(), Animal()] for animal in animals { if let dog = animal as? Dog { dog.bark() // as?による安全なキャスト } else { print("犬ではありません!") } }
主な特徴:
as!演算子を使用して、関連のない型(例えばStringからInt)にキャストできますか?それはどうなりますか?
as!演算子は常に実行時のクラッシュを引き起こします。型が互換性がない場合、またはそれらの間に継承や共通のプロトコルの階層がない場合です。根本的に異なる型間の変換は許可されていません。
コード例:
let value: Any = "abc" let num = value as! Int // クラッシュ: 'String'型の値を'Int'にキャストできませんでした
ヒエラルキーに関連がない型に対してas?を使用した場合、どうなりますか?
as?は、安全にキャストできない場合は常にnilを返します。型が継承や実装されているプロトコルと全く関連していない場合でも同様です。
コード例:
let value: Any = 5 if let str = value as? String { print(str) } else { print("Stringにキャストできません") // この枝が実行されます }
クラスの階層においてダウンキャストにasを使用することはできますか?
いいえ。as演算子はアップキャスト(例えば、DogからAnimalに、または実装されたプロトコルにキャストする場合)にのみ適しています。ダウンキャスティングには常にas?またはas!を使用します。
コード例:
let animal: Animal = Dog() // let dog = animal as Dog // コンパイルエラー let dog = animal as? Dog // 正しい方法
プロジェクトには[Any]のコレクションがあり、誤って文字列と数値が入れられていました。データ処理のために、開発者は複数の場所でlet value = item as! Stringと書いており、数値が配列に現れた際にクラッシュしていました。
長所:
短所:
関連型を持つenumを使用して書き直しました:
enum Payload { case text(String); case number(Int) } let data: [Payload] = [.text("abc"), .number(1)]
長所:
短所: