はじめに
NSMutableArray
から(Swiftの)Array
への変換には、以下のように2回のキャストが必要になります。
let mutableArray: NSMutableArray = Foo.someArray()
if let xs: [String] = mutableArray as NSArray as? [String] {
}
// or
if let xs: [String] = mutableArray as [AnyObject] as? [String] {
}
しかし、as
キーワードが2回も登場して、視覚的にどうもゴチャゴチャして読みづらく感じます。
ジェネリックなメソッド
すぐに思いつくのは、NSMutableArray
のextensionを作成して、そこでジェネリック(多相的)なメソッドを宣言することです。
extension NSMutableArray {
func asArray<T>() -> [T]? {
return self as NSArray as? [T] // Warning: Cast from 'NSArray' to unrelated type '[T]' always fails
}
}
コンパイラの”必ず失敗する”というWarningが気になりますが、これで一応動作はするようです。
追記:Swift 3.0 では Warning が表示されないことを確認しました。
以降でいくつか考察していますが、この書き方がベターかと思います。
if let xs: [String] = mutableArray.asArray() {
print(xs)
}
戻り値から型を推論するので、代入先での型宣言は必須です。
Warningが気になる
(私の手元の環境では)動作していますが、コンパイラのWarningが気になります。
Objective-Cであればclang
の#pragma
でWarningを消すことが出来ましたが、現状のSwift(2.3)の時点ではそのような制御構文は(たぶん)存在しません。
次善策として、キャストする型ごとにメソッドを用意してしまうというのも手でしょうか?
extension NSMutableArray {
var as_StringArray: [String]? { return self as NSArray as? [String] }
var as_IntArray: [Int]? { return self as NSArray as? [Int] }
...
}
if let xs = mutableArray.as_StringArray {
}
if let xs = mutableArray.as_IntArray {
}
型ごとにボイラープレートコードを書く必要がありますが可読性は上がります。ジェネリックverと異なり、戻り値で型が決定しているので、(あなたがそれを望むならば)代入先の型宣言は省略できます。
型ごとのコードを書くコストを受け入れる価値があるかどうかは、あなたのプロジェクトやチームに依存するでしょう。
.copy()
追記:
コメントにて @tomohisaota さんに.copy()
する書き方も教えて頂きましたので、追記させていただきました。
NSMutableArray
に対して.copy()
を呼び出して、NSArray
に変換するように書くことで、as
によるキャストを1回に抑えられます。
if let xs: [String] = mutableArray.copy() as? [String] {
}
一見すると無駄なコピーが発生しそうにも見えますが、パフォーマンス的には変化無いようです。(詳細はコメント欄を参照)
まとめ
ここまでに上げた3種類のキャストコードをまとめます。
let mutableArray: NSMutableArray = Foo.someArray()
// 1. 普通のキャスト(問題提起:可読性が悪い?)
if let xs: [String] = mutableArray as NSArray as? [String] {
}
// 2. ジェネリックなキャスト(メソッド宣言側でコンパイラWarningあり)
// 追記:Swift 3.0 では Warning なし
if let xs: [String] = mutableArray.asArray() {
}
// 3. 型ごとに特化したキャスト
if let xs: [String] = mutableArray.as_StringArray {
}
// 4. .copy()を利用したキャスト
if let xs: [String] = mutableArray.copy() as? [String] {
}