2020/1/31 Swift 型キャスト(Type Casting)

型キャストはインスタンスの型を調べる、あるいはそのインスタンスを、そのインスタンスの型のスーパークラス、あるいはサブクラスとして扱うことです。

Swiftの型キャストは

is

または

as

を用いて実装されます。この二つの演算子は簡潔で表現力のある型の調査方法、あるいは型を別の型へキャスト(変換)する方法を提供します。

型キャストのためにクラス階層を定義する

「特定のクラスのインスタンスの型を調べる」

「同じクラス階層の中で、あるクラスのインスタンスを他のクラスへ型キャストする」

これらの目的のために、クラス階層の中の型キャストを使用することができます。3つの継承関係のあるクラスと、それらのクラスのインスタンスを含む配列を定義し、型キャストのサンプルを作っていきます。

最初のクラスはベースクラスのMediaItemです。デジタルメディアライブラリに現れるいくつかの種類のアイテムの基本的な機能を表現します。String型のnameプロパティとイニシャライザを定義します。(全てのメディアアイテムが名前を有すると想定します。)

class MediaItem {
    var name: String
    init(name: String) {
        self.name = name
    }
}

次にMediaItemクラスのサブクラスを二つ定義します。一つはMovieクラス映画に関する情報をカプセル化するクラスです。directorプロパティと、それに対応したイニシャライザが定義されています。二つ目はSongクラスです。Songクラスにはartistプロパティとそれに対応するイニシャライザが追加されています。

class Movie: MediaItem {
    var director: String
    init(name: String, director: String) {
        self.director = director
        super.init(name: name)
    }
}

class Song: MediaItem {
    var artist: String
    init(name: String, artist: String) {
        self.artist = artist
        super.init(name: name)
    }
}

最後にlibraryという名の定数の配列を生成しています。この配列には二つのMovieクラスのインスタンス(Movieインスタンスと呼ぶ)と三つのSongクラスのインスタンス(Songインスタンスと呼ぶ)が含まれています。libraryの型は、配列リテラルを代入されることでSwiftから推定されます。SwiftのタイプチェッカーはMovieクラスとSongクラスの共通のスーパークラスMediaItemクラスをたどることができます。ということでlibrary配列の型は[MediaItem]型と推定されます。

let library = [
    Movie(name: "Casablanca", director: "Michael Curtiz"),
    Song(name: "Blue Suede Shoes", artist: "Elvis Presley"),
    Movie(name: "Citizen Kane", director: "Orson Welles"),
    Song(name: "The One And Only", artist: "Chesney Hawkes"),
    Song(name: "Never Gonna Give You Up", artist: "Rick Astley")
]
// the type of "library" is inferred to be [MediaItem]

定数libraryに格納されている要素はMovieクラスとSongクラスのインスタンスです。しかしこの配列の要素を一つずつ取り出して処理する時、受け取る一つ一つの要素はMovie型やSong型ではなくMediaItem型となります。これらの一つ一つの要素を、その固有の型として扱う(MovieインスタンスはMovie型として、SongインスタンスはSong型として)ためには、それぞれの型を調べる、あるいは他の型へダウンキャストする必要があります。以下で説明していきます。

型を調べる

インスタンスの型を調べるにはタイプチェックオペレータ( is )を使用します。

sample1-1ではmovieCountとsongCountという二つの変数を用意しています。これらはlibrary配列内のMovieインスタンスの数とSongインスタンスの数をカウントします。

sample1-1

class MediaItem {
    var name: String
    init(name: String) {
        self.name = name
    }
}

class Movie: MediaItem {
    var director: String
    init(name: String, director: String) {
        self.director = director
        super.init(name: name)
    }
}

class Song: MediaItem {
    var artist: String
    init(name: String, artist: String) {
        self.artist = artist
        super.init(name: name)
    }
}

let library = [
    Movie(name: "Casablanca", director: "Michael Curtiz"),
    Song(name: "Blue Suede Shoes", artist: "Elvis Presley"),
    Movie(name: "Citizen Kane", director: "Orson Welles"),
    Song(name: "The One And Only", artist: "Chesney Hawkes"),
    Song(name: "Never Gonna Give You Up", artist: "Rick Astley")
]

var movieCount = 0
var songCount = 0

for item in library {
    if item is Movie {
        movieCount += 1
    } else if item is Song {
        songCount += 1
    }
}

print("Media library contains \(movieCount) movies and \(songCount) songs")
// Prints "Media library contains 2 movies and 3 songs"

sample1-1ではlibrary配列の全ての要素を一つずつ取り出して処理します。繰り返しの全ての回でfor-inループは定数itemに配列内の次のMediaItemをセットします。

現在のMediaItemがMovieインスタンスの場合

item is Movie

はtrueを返します。そうでない場合falseを返します。同様に

item is Song

は現在の要素がSongインスタンスかどうかをチェックします。for-inループが最後まで終わると、movieCountとsongCountに配列内の各型のインスタンスの数がセットされた状態になります。


ダウンキャスト(Downcasting)

特定の型として型注釈された定数や変数が、実際にはそのサブクラスのインスタンスを参照している場合があります。その場合、タイプキャスト演算子(as?またはas!)を用いてサブクラスにダウンキャストすることができます。

ダウンキャストは失敗する可能性がありますので、タイプキャスト演算子は二つのフォームがあります。

一つ目、条件付きフォームであるas?

ダウンキャストしようとする型のオプショナル型を返します。(Int型にダウンキャストしようとしている場合Int?)

タイプキャスト演算子(条件付きフォーム)は、ダウンキャストが成功するかどうかわからない場合に使用します。この演算子は常にオプショナル型の値を返します。ダウンキャスト不可能な場合nilを返します。ですのでダウンキャストが成功したかをチェックすることができます。

二つ目、強制フォームであるas!

ダウンキャストと強制アンラップを試みます。失敗すると実行時エラーが発生します。

タイプキャスト演算子(強制フォーム)は、ダウンキャストが常に成功するとわかっている場合に使用します。

sample1-2

class MediaItem {
    var name: String
    init(name: String) {
        self.name = name
    }
}

class Movie: MediaItem {
    var director: String
    init(name: String, director: String) {
        self.director = director
        super.init(name: name)
    }
}

class Song: MediaItem {
    var artist: String
    init(name: String, artist: String) {
        self.artist = artist
        super.init(name: name)
    }
}

let library = [
    Movie(name: "Casablanca", director: "Michael Curtiz"),
    Song(name: "Blue Suede Shoes", artist: "Elvis Presley"),
    Movie(name: "Citizen Kane", director: "Orson Welles"),
    Song(name: "The One And Only", artist: "Chesney Hawkes"),
    Song(name: "Never Gonna Give You Up", artist: "Rick Astley")
]

for item in library {
    if let movie = item as? Movie {
        print("Movie: \(movie.name), dir. \(movie.director)")
    } else if let song = item as? Song {
        print("Song: \(song.name), by \(song.artist)")
    }
}

// Movie: Casablanca, dir. Michael Curtiz
// Song: Blue Suede Shoes, by Elvis Presley
// Movie: Citizen Kane, dir. Orson Welles
// Song: The One And Only, by Chesney Hawkes
// Song: Never Gonna Give You Up, by Rick Astley

sample1-2では、library配列内の全てのMediaItemをイテレートしています。そして各要素の適切な説明を表示します。この処理をするためには、各要素に対して、(MediaItem型としてではなく)Movie型、あるいはSong型としてアクセスする必要があります。要素の説明文の中で使用するdirectorあるいはartistプロパティにアクセスするために、Movie型、あるいはSong型としてアクセスする必要があります。

sample1-2では配列内の各要素はMovie型かもしれないし、Song型かもしれません。それぞれの要素が、Movie型へのダウンキャストが成功する要素か、Song型へのダウンキャストが成功する要素か、事前に確定していません。ですので、ダウンキャストするのにタイプキャスト演算子(条件付きフォーム)(as?)を使用するのが適切です。

sample1-2ではまず取り出した要素をMovie型へダウンキャストしようとします。itemはMediaItem型インスタンスとして受け取っていますので、itemはMovie型の可能性もあるし、Song型の可能性もあります。あるいはただのMediaItem型の可能性もあります。つまりitemが受け取るインスタンスがMovie型か、Song型か、MediaItem型か、不確定ですので、サブクラスへダウンキャストしようとした時、タイプキャスト演算子(条件つきフォーム)はオプショナル型の値を返します。

item as? Movie

の結果はMovie?(Optional<Movie>)型です。

library配列内のSongインスタンスをMovie型へダウンキャストしようとすると失敗します。これに対処するためsample1-2では、返されたMovie?型の値が実際に値を有しているか(つまりダウンキャストが成功したか)、をオプショナルバインディングでチェックしています。

if let movie = item as? Movie

は、「itemをMovie型とみなしてアクセスして、アクセスに成功すればmovieという名の一時的な定数に、返されたオプショナル型の値をアンラップしてセットする」ということです。

ダウンキャストが成功すればそのMovieインスタンスの説明文の表示にmovieのプロパティ(name,director)が使用されます。

NOTE

型キャストはインスタンスそのもの、あるいはその値を変えるものではありません。裏側にあるインスタンス自体は変化しません。キャストされた型として扱う、あるいはアクセスする、ということだけです。


Any型、AnyObject型の型キャスト

Swiftには、不特定な型を扱うための二つの特別な型が用意されています。

  • Any型は、(関数型を含む)「何らかの型(のインスタンス)」を表現することができます。
  • AnyObject型は、「何らかのクラス型(のインスタンス)」を表現することができます。

AnyあるいはAnyObjectは明示的にその振る舞いや機能が必要な場合のみ使うようにしましょう。型安全のため、必要な時以外はAny,AnyObjectは使わず、型を指定する方が良いでしょう。

以下のサンプルは別々の型のインスタンスを扱うためにAnyを用いている例です。関数型(クロージャー)やクラス以外の型も含まれています。things変数に、要素がAny型の配列をセットしています。

var things = [Any]()

things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append((3.0, 5.0))
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
things.append({ (name: String) -> String in "Hello, \(name)" })

thingsは二つのInt型の値、二つのDouble型の値、文字列値、(Double,Double)のタプル、Movie型のインスタンス、クロージャーを含んでいます。

工事中🏗

 

 

 

 

https://docs.swift.org/swift-book/LanguageGuide/TypeCasting.html

コメントを残す

メールアドレスが公開されることはありません。