Swiftの型キャスト(Type Casting)

Swiftのtype casting

型キャスト(Type Casting)です。型変換と和訳しても良いのですが、明示的に型自体を変換する訳では無いので、ここでは型キャストと呼ぶことにします。キャストは「役割」「配役」という意味があり、動詞だと「(役割を)割り当てる」という意味があります。

ここでは主に型キャスト用の演算子(isas)を使った、型のチェックとダウンキャスト(downcasting)について詳しく説明します。

型キャスト(Type Casting)って何?

使うプログラミング言語によって定義は異なると思いますが、Swiftにおける型キャスト(Type Casting)

という2つの意味で使うようです。2つ目はクラス(class)と明示してあることから分かるように、クラス特有のキャストになります。

Swiftの型キャストで使える演算子はisasです。直感的に分かるかもしれませんが、ざっくり言うと

  • isは型をチェックする演算子
  • asは型を割り当てる演算子

です。「割り当てる」というのは語弊があるかもしれませんが、あるインスタンスに対して最適な(本来持っている)型を割り当てるという感じです。

ここでのasは「〜として」「〜とみなす」という意味だと思います。これらの演算子の厳密な意味については後述します。

また、ある型がプロトコル(protocol)に準拠している(conform)かどうかの確認にも型キャストが使えるようです。これはprotocolのセクションで学ぶことになりそうです。

型キャストをテストするためのクラスを用意

具体的に型のチェック(checking type)とダウンキャスト(downcasting)をテストするために、継承(inheritance)を使ってクラス階層構造を持ったクラスを作ってみます。公式マニュアルではMediaItemという基底クラスを持ったMovieSongというサブクラスを作っていましたが、似たような構造のクラスを作ってみます。

// デバイスクラス
class Device {
    var name: String
    init(name: String) {
        self.name = name
    }
}

基底クラスとしては何かしらの機械的なモノ全てを表現するDeviceクラスを作ってみました。構造はMovieItemと全く一緒です。

次にDeviceを継承するサブクラスを2つ作ってみます。

// デスクトップPC
class DesktopComputer: Device {
    var os: String
    init(name: String, os: String) {
        self.os = os
        super.init(name: name)
    }
}

// 携帯電話
class MobilePhone: Device {
    var displaySize: Int // インチ
    init(name: String, displaySize: Int) {
        self.displaySize = displaySize
        super.init(name: name)
    }
}

1つはデスクトップPC(DesktopComputer)、もう1つは携帯電話(MobilePhone)です。簡単のためそれぞれ1つずつ格納プロパティ(stored property)を用意して、初期化子で初期化しています。スーパークラスの初期化子も忘れずに呼びます(初期化の委譲)。

最後に今作ったクラスを配列(Arrayにまとめて入れてみます。

// device定数の型はDeviceになる
let device = [
    DesktopComputer(name:"Mac mini", os:"OSX El Capitan"),
    MobilePhone(name:"iPhone 6s Plus", displaySize:5.5),
    MobilePhone(name:"iPhone 6s", displaySize:4.7)
]

今、配列の型を指定してないので、Swiftは各要素が共通に持つスーパークラス(Device)を配列の型として型推論(type inference)します。

型推論自体には問題がありませんが、実際にこの配列要素をfor-in文などのループで取り出して使用する場合、型がDeviceとして取り扱われるため、サブクラスが持つプロパティにアクセスすることが出来ません。

// 配列要素を取り出す例
for item in device {
    if item.name == "Mac mini" {
        print("\(item.os)") // <-- ここでコンパイルエラー
    }
}
//コンパイルエラー
//error: value of type 'Device' has no member 'os'

このような場合に、元々の型(今回ですとDesktopComputerMobilePhone)として配列要素を取り扱うために、型のチェックやダウンキャストを使う必要があります。

型のチェック | Checking Type

先ず型のチェックを見ていきます。先程も簡単に述べましたが、型のチェックにはis演算子を使います。この演算子は、厳密には

is演算子: あるインスタンスが(何かしらの)サブクラスかどうかチェックする演算子

です。もしインスタンスがチェックしているサブクラスならBool値の真(true)を返し、そうでないなら偽(false)を返します。

公式マニュアルと同じように、is演算子を使ってサブクラスのチェックをしてみると

// is演算子を使ってサブクラスをチェック
var numberOfDesktops = 0
var numberOfMobiles = 0

for item in device {
    if item is DesktopComputer {
        numberOfDesktops += 1
    } else if item is MobilePhone {
        numberOfMobiles += 1
    }
}
print("Deviceは\(numberOfDesktops)個のデスクトップPCと\(numberOfMobiles)個のモバイルフォンを含んでいます")
//"Deviceは1個のデスクトップPCと2個のモバイルフォンを含んでいます"と表示

のようになります。先程作ったdevice配列に対して、for-in文でループを回して中身を取り出し、取り出した配列要素をis演算子でチェックする、というプログラムになっています。

device配列に入っているサブクラスの数を確認するために、整数のnumberOfDesktopsnumberOfMobilesを用意して、is演算子がtrueを返したら(つまり、配列要素がチェックしているサブクラスに該当する場合)インクリメントするようにしています。

Swift3からインクリメント++(デクリメント--)演算子は無くなるので、複合代入演算子+=を使っています。

ダウンキャスト | Downcasting

ダウンキャスト(Downcasting)って何?

ダウンキャスト(Downcasting)とは、

スーパークラスとして扱われているインスタンスを、サブクラスとして取り扱うこと

です。ここで重要なのは、今注目しているインスタンス自身が実際にはサブクラスのインスタンスである、ということです。それがあたかもスーパークラスのインスタンスとして扱われている場合に、サブクラスとして取り扱うことをダウンキャストと呼んでいます。したがって、そもそもインスタンスがスーパークラスのインスタンスである場合にはダウンキャストは適用出来ません。

また、ここでの「ダウン」というのは、継承する方向を下向き(down)と見ているので、ダウンキャスト(downcast)なんだと思います。ダウンキャストしたい時に使える演算子が型キャスト演算子のas?as!です。

逆に、「サブクラスをスーパークラスにキャストすること」をアップキャストと呼びます。ダウンキャストは失敗する可能性がありますが、アップキャストは必ず成功します。

2つの型キャスト演算子: as?とas!

ダウンキャストは必ず成功するとは限りません。先程作ったクラスもそうですが、サブクラスは複数存在する可能性があります。例えば、DesktopComputerとして作ったインスタンスを、MobilePhoneにダウンキャストしようとすれば、当然ダウンキャストは失敗します。

「失敗する」というフレーズと、2つある演算子から想像が付くと思いますが、2つの型キャスト演算子の関係はオプショナル型implicitly unwrapped optionalsの関係のようなものです。

条件付き型キャスト演算子as?

条件付きの型キャスト演算子as?は、戻り値としてオプショナル型を返します。この演算子を使う時は、ダウンキャストが成功するかどうか分からない時、つまりサブクラスが本当に指定したサブクラスかどうか分からない時、です。ダウンキャストに失敗すると、結果はnilになります。

強制型キャスト演算子as!

強制型キャスト演算子as!を使う時は、ダウンキャストが絶対成功すると確信できる場合、つまりサブクラスが間違いなく指定したクラスに合致する場合、です。ただ、確信しているという主体は自分で、as!自体は単にあるインスタンスを指定した型に強制的にキャストするだけです。したがって、もしダウンキャストが失敗した場合は、実行時エラーを起こすので注意が必要です。

公式マニュアルによると、x as! Tの振る舞いは、(x as? T)!の振る舞いと等価のようです。

as演算子はないのか?

「ただのas演算子はないのか?」というのは当然の疑問ですが、あります。ただ、用途が違うようで、?!がつかないas演算子は、アップキャスト(upcast)とブリッジング(bridging)といった、コンパイル時にキャストが必ず成功するケースで使えるようです。

“The as operator performs a cast when it is known at compile time that the cast always succeeds, such as upcasting or bridging. Upcasting lets you use an expression as an instance of its type’s supertype, without using an intermediate variable.”

抜粋:: Apple Inc. “The Swift Programming Language”。 iBooks https://itun.es/jp/jEUH0.l

as演算子は、この後説明しますが、Any型の配列から特定の型を取り出す時に出て来ます。

as?演算子を使ったダウンキャストの具体例

先程作ったクラスを使って、ダウンキャストを実行してみます。

// as?演算子でダウンキャスト
for item in device {
    if let pc = item as? DesktopComputer {
        print("デスクトップ: \(pc.name), OS: \(pc.os)")
    } else if let mobile = item as? MobilePhone {
        print("モバイル: \(mobile.name), ディスプレイの大きさ= \(mobile.displaySize)インチ")
    }
}
//デスクトップ: Mac mini, OS: OSX El Capitan
//モバイル: iPhone 6s Plus, ディスプレイの大きさ= 5.5インチ
//モバイル: iPhone 6s, ディスプレイの大きさ= 4.7インチ

一つ前の例と同様にfor-inループで配列要素を取り出しています。取り出した配列要素itemは、サブクラスのDesktopComputerMobilePhoneか分かりません。したがって、このケースではas?演算子を使うのがベストです。

また、as?演算子は戻り値としてオプショナル型を返しますので、型本体を取り出すためにoptional bindingを使っています。つまり、

if let pc = item as? DesktopComputer {

というコードは、オプショナル型のDesktopComputer?を返す演算部分

item as? DesktopComputer

と、そのオプショナル型を外すoptional binding部分

if let pc = (DesktopComputer?) {

が組み合わさった形になっています。

上記のコードで、ちゃんとプロパティの値が出力出来ていることが分かります。型キャスト演算子でダウンキャストすることによって、スーパークラスDeviceの型として取り扱われていた配列要素を、サブクラスの型として取り出すことが出来ました。ただ、公式マニュアルに注意書きがありますが、型キャストによってインスタンス自身は改変されません。型キャストは、インスタンスをどの型として取り扱うか、またはアクセスするか、を決めているだけのようです。

“NOTE

Casting does not actually modify the instance or change its values. The underlying instance remains the same; it is simply treated and accessed as an instance of the type to which it has been cast.”

抜粋:: Apple Inc. “The Swift Programming Language”。 iBooks https://itun.es/jp/jEUH0.l

AnyまたはAnyObjectへの型キャスト

Anyは全部、AnyObjectはクラス限定

Swiftでは、AnyObjectAnyという特別なtype aliases(敢えて和訳を当てると「型の別名」でしょうか?)を使うことによって、特定の型に依らないコードを実装出来るようです。

  • AnyObjectは、あらゆるクラス型のインスタンス
  • Anyは、あらゆる型のインスタンス(関数型も含む)

という住み分けのようです。Anyがあらゆる型に対して適用可能なのに対して、AnyObjectはクラス限定です。

ただ、公式マニュアルでは、AnyAnyObjectは本当にそれが必要な場合のみ使うべきで、通常はちゃんと型を特定してコードを書くのがベターだと言ってます。

“Use Any and AnyObject only when you explicitly need the behavior and capabilities they provide. It is always better to be specific about the types you expect to work with in your code.”

抜粋:: Apple Inc. “The Swift Programming Language”。 iBooks https://itun.es/jp/jEUH0.l

AnyObject

Cocoa APIを使った時に、AnyObjectの配列[AnyObject]を返り値として受け取ることがあるようです(私は未だCocoa APIに触れたことがないので良く分かりません)。大抵の場合は、APIが配列の型情報をちゃんと提供しているようなので、「この配列は◯◯という型だな」という感じで型を特定出来るようです。

このように、AnyObjectの配列でも型が特定出来るようなケースでは、強制型キャスト演算子as!が使えます。

// AnyObject配列
let mobiles: [AnyObject] = [
    MobilePhone(name: "iPhone 6", displaySize: 4.7),
    MobilePhone(name: "iPhone 6 Plus", displaySize: 5.5),
    MobilePhone(name: "iPhone SE", displaySize: 4.0),
]

// for-inで取り出してから、as!でキャスト
for item in mobiles {
    let iphone = item as! MobilePhone
    print("モバイル: \(iphone.name), ディスプレイの大きさ= \(iphone.displaySize)インチ")
}
//モバイル: iPhone 6, ディスプレイの大きさ= 4.7インチ
//モバイル: iPhone 6 Plus, ディスプレイの大きさ= 5.5インチ
//モバイル: iPhone SE, ディスプレイの大きさ= 4.0インチ

公式と似たような例ですが、AnyObjectの配列を作り、要素として全てMobilePhoneインスタンスを入れました。この場合、配列要素の型は自明ですからas!演算子を使って安全にダウンキャスト出来ます。

上記の例は、(1)配列要素を取り出す、(2)as!で要素をダウンキャスト、という2段階のコードになっていますが、

// as!で配列全体をキャスト
for iphone in mobiles as! [MobilePhone] {
    print("モバイル: \(iphone.name), ディスプレイの大きさ= \(iphone.displaySize)インチ")
}

のように、配列全体をダウンキャストすることも可能のようです。この書式は便利ですね。

Any

Anyはあらゆる型に対応しているということなので、実験してみます。

// Any型配列に色々入れてみる
var array = [Any]()
array.append(1)
array.append(3.14)
array.append("hello")
array.append(true)
array.append([2,4,6])
let arraySet: Set = [1,3,5]
array.append(arraySet)
array.append(["師走": 12, "睦月": 1])
array.append((3.14, 2.71828))
array.append(MobilePhone(name:"iPhone SE", displaySize: 4.0))
array.append({ (name: String) -> String in "こんにちは、\(name)" })

Any型の配列を作って、色々入れてみました。基本型が入るのはもちろん、配列やtuple、クラスも入りますし、関数型のクロージャ(closures)も入ります。実用性があるかどうかは別にして、このようにあらゆる型が入った配列を作る場合はAny型が便利です。クラス限定ならAnyObjectです。

中身を取り出す時はswitchを使います。

// switch文でAny型配列要素を取り出す
for item in array {
    switch item {
    case let someInt as Int:
        print("整数: \(someInt)")
    case let someDouble as Double where someDouble>0.0:
        print("正のDouble: \(someDouble)")
    case is String:
        print("文字列: \(item)")
    case let someBool as Bool:
        print("論理値")
    case let arrayInt as Array:
        print("整数の配列: \(arrayInt)")
    case let setInt as Set:
        print("整数の集合: \(setInt)")
    case is Dictionary:
        print("型の辞書")
    case let (pi, e) as (Double, Double):
        print("pi=\(pi), e=\(e)")
    case let phone as MobilePhone:
        print("携帯電話: \(phone.name)")
    case let greeting as (String) -> String:
        print(greeting("太郎"))
    default:
        print("該当なし")
    }
}

caseを使ってキャストする場合はas演算子で取り出せるようです。

入れ子になった型 | Nested Types

以前入れ子にした関数(nested functions)というのを学びましたが、型を入れ子にすることも可能です。公式のドキュメントには、列挙型(enumerations)は良く入れ子構造にして、クラスや構造体の機能をサポートする場合がある、と書いてありますが、基本的にはクラスや構造体、または列挙型などあらゆる型を入れ子にすることが可能です。

入れ子にするのは簡単で、ある特定の型の中に別の型を定義するだけです。

// 構造体の中に列挙型
struct SomeStructure {
    enum NestedEnumeration {
    }
}

このコードでは、構造体の中に列挙型を定義しています。また、

// 複数の階層化も可能。階層化の制限は無い
class SomeClass {
    struct NestedStructure {
        class NestedClass {
        }
    }
}

このように型を入れ子にする場合(クラスの中に構造体、その中にクラス)、その階層の深さに制限はありません。

入れ子になった型の具体例 | 西暦と和暦の変換

入れ子になった型を使って、西暦を和暦に変換(逆変換も可能)出来る構造体を作ってみました。

少し長いですが、先にプログラム全体を載せます。西暦(和暦)を入力すると、和暦(西暦)を自動的に取り出せるような構造体です(昭和と平成しか実装していませんが)。

import Foundation

// 西暦 <--> 和暦変換
struct Year {
    var year: Int // 西暦, eg. 2016
    var jyear: JapaneseEra // 和暦, eg. 平成28年

    struct JapaneseEra {
        // 西暦 -> 和暦
        static func ad2j(ad: Int, koffset: Int, hoffset: Int) -> Int {
            return ad - koffset + hoffset
        }

        // 和暦 -> 西暦
        static func j2ad(j: Int, koffset: Int, hoffset: Int) -> Int {
            return j + koffset + hoffset
        }

        enum 元号: String {
            case 昭和 = "昭和"
            case 平成 = "平成"
            case 無し = ""
        }
        var title: 元号
        var number: Int // 元号に続く数字、例えば「平成28年」の「28」
        var name: String {
            return "\(title.rawValue)\(number)年"
        }
        
        // 西暦で初期化
        init(year: Int) {
            switch year {
            case 1926...1989:
                self.number = JapaneseEra.ad2j(ad:year, koffset:1900, hoffset:-25)
                self.title = 元号.昭和
            case 1990...2016:
                self.number = JapaneseEra.ad2j(ad:year, koffset:2000, hoffset:12)
                self.title = 元号.平成
            default:
                self.number = 0
                self.title = 元号.無し
            }
        }
        
        // 和暦で初期化
        // 想定しているフォーマット: s60, h20など
        init(year: String) {
            let start = year.startIndex
            let title = year[start]
            let value = Int(year.substring(from: year.index(after: start)))
            if value == nil {
                print("入力した年が不正: year=\(year)")
                self.number = 0
                self.title = 元号.無し
                return
            }
            
            switch title {
                case "s", "S":
                    self.number = value!
                    self.title = 元号.昭和
                case "h","H":
                    self.number = value!
                    self.title = 元号.平成
                default:
                    self.number = 0
                    self.title = 元号.無し
            }
        }
        
        // 和暦 -> 西暦
        var ad: Int {
            switch title {
            case .昭和: return JapaneseEra.j2ad(j:number, koffset:1900, hoffset:25)
            case .平成: return JapaneseEra.j2ad(j:number, koffset:2000, hoffset:-12)
            default: return 0
            }
        }
    }

    // 西暦で初期化, eg. init(year: 2016)
    init(year: Int) {
        self.year = year
        self.jyear = JapaneseEra(year: year)
    }
    
    // 和暦で初期化, eg. init(year: "H27")
    init(year: String) {
        self.jyear = JapaneseEra(year: year)
        self.year = jyear.ad
    }
}

順番に見ていきます。

西暦と和暦プロパティ。和暦用構造体を用意

struct Year {
    var year: Int // 西暦, eg. 2016
    var jyear: JapaneseEra // 和暦, eg. 平成28年
....

必要なのは西暦を表す整数と、和暦を表す何かしらのプロパティです。和暦は「平成28年」とか「H28」とかにしたかったので、基本的にはStringですが、その他もろもろの機能を考えて構造体JapaneseEraにしています。

JapaneseEraという和暦を表すプロパティが、入れ子になった型になっています。

和暦を表す構造体JapaneseEra

西暦から和暦への変換、またはその逆変換は、この構造体の中に実装しています。この構造体が持っているプロパティを見ると、

struct Year {
....
    struct JapaneseEra {
        enum 元号: String {
            case 昭和 = "昭和"
            case 平成 = "平成"
            case 無し = ""
        }
        var title: 元号
        var number: Int // 元号に続く数字、例えば「平成28年」の「28」
        var name: String {
            return "\(title.rawValue)\(number)年"
        }
....
}

和暦の持つプロパティは2つで、元号を表す「元号」という名前の列挙型(日本語で定義できるのがうれしいです)、元号の後ろに付く数字number(整数)です。この元号を表す列挙型が、JapaneseEra構造体の中で定義されていますので、3段の入れ子構造になっています(Yearの入れ子であるJapaneseEraの入れ子、という構造)。列挙型「元号」は、raw valueとしてString型を持っていて、単に元号を表すためだけのものです。今は「昭和」と「平成」しか実装していません。

また、JapaneseEra構造体は、計算型プロパティ(computed property)nameを使って「平成28年」のような形の文字列を返すことも出来ます。

西暦を和暦に変換

実際に西暦を和暦に変換するコードは、初期化子内部に実装されています。

struct Year {
....
    struct JapaneseEra {
....
        // 西暦で初期化
        init(year: Int) {
            switch year {
            case 1926...1989:
                self.number = JapaneseEra.ad2j(ad:year, koffset:1900, hoffset:-25)
                self.title = 元号.昭和
            case 1990...2016:
                self.number = JapaneseEra.ad2j(ad:year, koffset:2000, hoffset:12)
                self.title = 元号.平成
            default:
                self.number = 0
                self.title = 元号.無し
            }
        }
....
    }
}

初期化子のパラメータが西暦(例えば2016とか)になっています。これをswitch文で条件分岐し、和暦用の元号を決め、その後に続く数字を計算しています。条件分岐は範囲演算子(range operators)を使って行っています。if文でも全く同様に条件分岐出来ますが、switch文を使った方が綺麗に書ける気がします。

西暦から和暦への変換の計算自体は足し算と引き算の組み合わせですが、同じような計算をするので型メソッドad2j(ad:koffset:hoffset)を使って一般化してあります。

// 西暦 -> 和暦
static func ad2j(ad: Int, koffset: Int, hoffset: Int) -> Int {
    return ad - koffset + hoffset
}

// 和暦 -> 西暦
static func j2ad(j: Int, koffset: Int, hoffset: Int) -> Int {
    return j + koffset + hoffset
}

全く同じ計算(符号は反転)ですが、和暦から西暦への変換も別の型メソッドとして用意してあります。ただ、本当に簡単な四則演算なので、わざわざメソッド化する必要はないかもしれません。

1989年は昭和と平成の境目で、昭和64年と平成元年が混在しているのですが、今回は実装していません。実装する場合は、新しい構造体などを作ってプロパティを増やすか、1989年だけ特殊な処理をするような形にしないといけません。

和暦から西暦に変換

和暦から西暦に変換する機能も実装します。

struct Year
....
    struct JapaneseEra {
        ....
        // 和暦で初期化
        // 想定しているフォーマット: s60, h20など
        init(year: String) {
            let start = year.startIndex
            let title = year[start]
            let value = Int(year.substring(from: year.index(after: start)))
            if value == nil {
                print("入力した年が不正: year=\(year)")
                self.number = 0
                self.title = 元号.無し
                return
            }
            
            switch title {
                case "s", "S":
                    self.number = value!
                    self.title = 元号.昭和
                case "h","H":
                    self.number = value!
                    self.title = 元号.平成
                default:
                    self.number = 0
                    self.title = 元号.無し
            }
        }

        // 和暦 -> 西暦
        var ad: Int {
            switch title {
            case .昭和: return JapaneseEra.j2ad(j:number, koffset:1900, hoffset:25)
            case .平成: return JapaneseEra.j2ad(j:number, koffset:2000, hoffset:-12)
            default: return 0
            }
        }
        ....
    }
}

入力する文字列は、例えば「H28」で、それを西暦(2016など)に変換します。今回は元号を英語一文字(SH)でしか受け付けませんが、普通に「昭和」などで受け取れるように改良することも出来ます。

let start = year.startIndex
let title = year[start] // SかH
let value = Int(year.substring(from: year.index(after: start))) // 数字(文字列)だけを取り出して整数に変換

和暦は文字列として受け取り、その頭文字から元号を判定して、残った数字を文字列から整数に変換しています。Stringから部分的に文字列を抜き出すのにsubstring(from:)メソッドを使っています。

import Foundationの記述がないとsubstring(from:)メソッドを呼び出せなかったので、サンプルコードの頭で読み込んでいます。

StringIntに型変換するとオプショナル型になるので、その後でnilかどうかのチェックを行っています。

初期化子では、和暦用の元号と数字を収めるだけで、実際の変換は計算プロパティadで行っています。

// 和暦 -> 西暦
var ad: Int {
    switch title {
        case .昭和: return JapaneseEra.j2ad(j:number, koffset:1900, hoffset:25)
        case .平成: return JapaneseEra.j2ad(j:number, koffset:2000, hoffset:-12)
        default: return 0
    }
}

インスタンスを作って試しに変換

// 西暦から和暦
let year = Year(year: 2016)
print(year.jyear.name)
//"平成28年"と表示

// 和暦から西暦
let jyear = Year(year: "H28")
print(jyear.year)
//"2016"と表示

試しに2016年をパラメータにして、西暦から和暦、または和暦から西暦への変換をやってみると、どちらもちゃんと動いていることが分かります。

入れ子になった型を外から使う

タイトルのままですが、入れ子になった型(ここだと例えばenum 元号)をスコープの外側から呼び出す場合、入れ子にしている親の型を付けて呼び出します。具体的には、例えば

print(Year.JapaneseEra.元号.昭和.rawValue)

という感じです。

入れ子構造は、C言語で言う所の名前空間のように、スコープを限定するための機能として使っても良いかもしれません。

まとめ

  • 型キャストは、型のチェック(is)、またはクラスのキャスト(ダウンキャスト:as?as!、アップキャスト・ブリッジング:as
  • as?はオプショナル型を返し、as!は強制的にダウンキャストする
  • Anyはあらゆる型、AnyObjectはあらゆるクラス型を表すtype aliases
  • 型の入れ子も可能。入れ子になっている型をスコープ外から呼び出す際には、入れ子にしている型をdot syntaxで連結する