Swiftで配列に違う型を入れられる件

Arrayをテストした時のXcodeのスクリーンショット

知ってる人には当たり前かもしれませんが、初心者には予想しなかった挙動でしたので、最初は少しびっくりしました。アプリ開発にはまだまだ遠いので、今はAny型配列の恩恵が良くわかっていませんが、将来必要になる時がくるかもしれません。

追記: Swift 3から振る舞いが変わり、配列に様々な型の値を入れるとコンパイルエラーになるように挙動が改善されました。Swift 3の場合にどうなるのか?という点について補足しています。

配列は同じ型の値を格納するためのもの?

Swift 2.xでの挙動

そもそも配列の定義が「同じ型を持つ値を順番に格納する」コレクション型ですので、違う型を入れることはできない。と思った時期もありました。ところが、Xcodeのplayground上ではコンパイルエラーにはなりません。

var arrayMultipleTypes = [1, 3.14, "hello"]
// 問題なし

配列のページを書いていた時に「こういうことをするとエラーになるよ」と、ドヤ顔で記事を書こうと思っていました。しかし、Xcode playgroundでテストしてみると正常にコンパイルされて、ぶったまげました。配列が定義通りなら、上記のように型が混在する配列はコンパイルエラーになるはずですが、そうはなってません。

もちろん配列の型を明示的に指定した場合なら、期待通りコンパイルエラーになります。

// Double型のArray
var arrayDouble: [Double] = [3.14]
let someInt = 1
arrayDouble.append(someInt) // これはだめ
arrayDouble.append("hello") // これもだめ
arrayDouble.append(1.5) // これなら通る

補足: Swift 3での挙動

と、ここまではSwift 2.x系での配列の挙動ですが、Swift 3からはコンパイルエラーを起こすようになりました。

var arrayMultipleTypes = [1, 3.14, "hello"]
//error: heterogenous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional

この次に説明しますが、Swift 2.x系では型推論で自動的にAny型として定義されていた配列が、明示的に指定しない場合はコンパイルを通さないよう設計変更されたみたいです。これは良い変更だと思います。

Any型というものがある

調べてみるとAny型というものがあることを知りました。これは公式マニュアルによると

“Swift provides two special types for working with nonspecific types:

  • Any can represent an instance of any type at all, including function types.
  • AnyObject can represent an instance of any class type.

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 (Swift 3)”。 iBooks https://itun.es/jp/jEUH0.l

だそうで、基本的な型の混在だけでなく、トゥープル(タプル、tuple)、関数やクロージャ等も値として取り得ます。

ですから、Xcodeのplayground上での配列は、何も指定しなければAnyになるのではないか、と思いました。Any型での配列では、以下のように色々な型を混在させることが可能です。

// Any型のArray
var arrayAny: [Any] = []
arrayAny.append(1)
arrayAny.append(1.5)
arrayAny.append("string")
arrayAny.append((2.0, "hello"))

これで解決した。と思いましたが、どうやら違うようでした。

どうも一番最初のように配列を初期化した場合NSObjectというObject-Cの型を使用するようです。

// 試しにtupleを追加
var arrayMultipleTypes = [1, 3.14, "hello"]
arrayMultipleTypes.append((1.0, 5, "a"))
//cannot convert value of type '(Double, Int, String)' to expected argument type 'NSObject'
繰り返しになりますが、この挙動はSwift 2.xまでで、Swift 3ではそもそも配列定義の時点でコンパイルエラーを起こします

NSObject?

Object-Cのことは全く分かりませんが、NSObjectというのはObject-Cクラスの最も基本的なクラスみたいです。クラス継承の際の基本的なインターフェースを備えているようなものでしょうか。

多分、tupleがSwiftで導入されたものなので、Object-CベースのNSObjectではtupleを取り扱えないということだと思います。また、クラスのインスタンス限定でAnyObjectという型も用意されているようで、挙動としてはこちらがデフォルトの混在Arrayと近い印象でした(出てきたエラーは異なりますが)。

// AnyObjectのArray
var arrayAnyObject: [AnyObject] = [1, 3.14, "hello"]
arrayAnyObject.append((2.0, "hello"))
// error: argument type '(Double, String)' does not conform to expected type 'AnyObject' arrayAnyObject.append((2.0, "hello"))

上記のサンプルはSwift 2.xのもので、同じコードをSwift 3で実行すると

var arrayAnyObject: [AnyObject] = [1, 3.14, "hello"]
//error: value of type 'Int' does not conform to expected element type 'AnyObject'

という具合に、AnyObjectを要素として指定した場合、Swiftの基本型が入るような配列を作ることが出来なくなっています。AnyObjectはクラス用の万能型であるという点と、Swiftの基本型は構造体である、ということから上のサンプルコードのコンパイルエラーが理解出来ると思います。

参考

スポンサーリンク
広告1
広告1

シェアする

フォローする