SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

アップルの新プログラミング言語「Swift」を探検しよう

Swiftの「クラス」と「ストラクチャ」 その② ~ subscript、タイプキャスト、メモリ管理(およびクラス、ストラクチャ、列挙型のおさらい)

アップルの新プログラミング言語「Swift」を探検しよう 第5回

タイプチェックとタイプキャスト

 Swiftでも、動作時におけるタイプチェック(型チェック)に関しての機能が提供されています。

AnyとAnyObject

 Swiftでは、デフォルトの方法で宣言したクラスはすべて基底クラスとして扱われると前回述べました。包括的にすべての型を扱うための手段として、Objective-CにおけるNSObject型のような一般的なクラスに対する基底クラスは用いることがSwiftではできません。しかし、代わりにAnyObjectAnyというタイプエイリアス[2]が用意されています。このタイプエイリアスの実態はプロトコルです。

[2] タイプエイリアスとは既存の型に他の名前を付けたものです。Swiftではtypealiasキーワードで他の型を別の名前の型として扱うことができます。

AnyObject

 すべてのクラスはAnyObjectのプロトコルに準拠した型として扱うことができます。

class Solid {}
class Fluid {}
class Gas {}
let states: [AnyObject] = [Solid(), Fluid(), Gas()]

 この例では、それぞれ基底クラスとして宣言されたSolid、Fluid、Gasクラスのオブジェクトを共通のAnyObjectとして扱い、それらを要素とする配列に代入しています。

Any

 すべてのクラス、ストラクチャ、列挙型、タプルはAnyのプロトコルに準拠した型として扱うことができます。

class Val {}
var some: Int? = 1
let values: [Any] = [1, "string", Val(), some, (1, "string")]

 この例では様々な型Int、String、Val、Optional>int<、(Int, String)をAnyとして扱い、それを要素とする配列に代入しています。

タイプチェックのis

 Objective-Cでは動作時のクラスタイプチェックを、NSObjectのインスタンスメソッドisKindOfClass:で行うことができました。

NSString *string = @"string";
BOOL isString = [string isKindOfClass:[NSString class]]; // インスタンスがNSString またはそのサブクラスであるかどうか判定

 Swiftでもis演算子を用いてタイプチェックを行うことができます。

class Place {
    var discount: UInt
    init(_ discount: UInt) {
        self.discount = discount
    }
}
class Factory : Place{}
class Store : Place{}

let places: [Place] = [Factory(400), Store(200), Factory(700), Store(100), Store(150)]
var discountOfFactories: UInt = 0
var discountOfStores: UInt = 0
for place in places {
    if place is Factory {
        discountOfFactories += place.discount
    } else if place is Store {
        discountOfStores += place.discount
    }
}
discountOfFactories // 1100
discountOfStores    // 450

 is演算子は左辺にインスタンス、右辺に指定の型を置くことでインスタンスが指定の型であるかどうかをBool値で返します。

 ただし、型の情報がコンパイル時にはっきりわかっている場合、結果がtrueであるかfalseあるかにかかわらずコンパイルエラーになります。

class Food {}
class Fish : Food {}

let someFish = Fish()
let someFood = Food()

someFish is Fish // コンパイルエラー
someFood is Fish // コンパイルエラー

 この例の場合、someFish、someFoodはコンパイル時にいずれも型がはっきりしているために、双方isでタイプチェックを行おうとするとコンパイルエラーとなります。

タイプキャストのas

 Objective-Cでは次の例のように、値の前に括弧を付けることでタイプキャストを行うことができました。

NSArray *values = @[@"string", @(2), [NSURL new]]; // 様々な型の値を持った配列を用意
NSInteger lengthOfString = ((NSString *)values[0]).length // 配列0番目のStringの長さを取り出す

 Swiftではas演算子を用いて、より安全に値をタイプキャストすることが可能です。

 Objective-Cでは動的なキャスト時に、インスタンス本来の型とキャスト先の型に不一致があっても、何も起きませんでした。Swiftでは、タイプキャスト時に型の不一致を検出した時点での挙動が定められています。

 まず、as演算子には2種類の形式があります。is演算子の評価時と同様に、as演算子も左辺にインスタンス、右辺に指定の型を置くことで次の2種類の形式に応じた結果を返します。

as?形式

 タイプキャストの結果をオプショナル列挙型にくるむことで、タイプキャストの安全性を高めることができます。

タイプキャストが成功した場合
指定の型とした値をオプショナル列挙型にくるんで返す。

タイプキャストが失敗した場合
nilとして値を返す。

as形式

 タイプキャストが失敗しないという確信があるときにのみ利用します。as?形式とは異なり、ランタイムエラーが起きる可能性があるため、使用時には注意を要します。

タイプキャストが成功した場合
指定の型として値をそのまま返す。

タイプキャストが失敗した場合
ランタイムエラーを起こす。

 as?形式とas形式の使用例を見てみましょう。

var values: [Any] = [1, 2, 3, "string", 5]

/* as?形式 */
var index = 0
for value in values {
    if let string = value as? String {
        break
    }
    index++
}
index // 3

/* as形式 */
for value in values {
    var integer = value as Int // ループ4回目でランタイムエラー
    println(integer)
}

 この例では、1つ目のループでas?を用いています。本連載第3回で解説したオプショナルバインディングを用いることで、

value as? String

の結果がnilではない場合に、breakしてループを抜けます。配列valuesを初めから走査していき、最初のString型の値の番号がindexによってわかるという仕組みです。

 2つ目のループでは、as形式を用いて変数valueをInt型にタイプキャストして変数integerに代入しています。4回目のループでは変数valueに"string"が代入されるため、タイプキャストが失敗してランタイムエラーが発生します。

 また、AnyやAnyObjectの配列がより具体的な型の配列であるとわかっている場合には、配列ごとタイプキャストすることも可能です。

class Solid {}
class Fluid {}
class Gas {}
let states: [AnyObject] = [Solid(), Solid(), Solid()]
for value in states as [Solid] {
   // この中でvalueはSolid型として扱うことができる
}

switch文でのisとas

 is演算子によるタイプチェックや、as演算子によるタイプキャストは、次の例のようにして、switch文のcase条件内で行うことができます。

class Animal{}
class Plant{}
var values: [Any] = ["name", 1, 1.3, Animal(), Plant()]

for value in values {
    switch value {
    case 1 as Int: println("one") // (1)
    case 1.0 as Double: println("one point zero") // (2)
    case is Plant: println("plant") // (3)
    case let string as String: println("string" + string) // (4)
    default: println("other")
    }
}
実行結果
stringname
one
other
other
plant

 この例のcaseでは、それぞれ次の条件が成立したときに、コロン以降の処理が実行されます。

(1) valueの値をInt型へタイプキャストでき、かつvalueの値が1である。
(2) valueの値をDouble型へタイプキャストでき、かつvalueの値が1.0である。
(3) valueの値の型がPlantである。
(4) valueの値をString型へタイプキャストでき、かつstringへvalueの値を代入できる。

次のページ
ARC

この記事は参考になりましたか?

アップルの新プログラミング言語「Swift」を探検しよう連載記事一覧

もっと読む

この記事の著者

yad(ヤド)

クラスメソッド株式会社のアプリケーションエンジニア。iPhoneアプリケーションの開発に2年以上従事している。開発に使用するObjective-Cのみならず、関数型言語Haskellや機械学習などにも関心がある。「Developers.IO」に寄稿した記事の一覧

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/8120 2014/09/29 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー