概要
配列の要素のアクセスにて、クラッシュする”[]”の代わりに、nilを返す”[safe: ]”を追加して使う方法です。
序文
SwiftではArrayのインデックスに範囲外の値を指定するとクラッシュします。
let array = ["A", "B", "C"] let index = 3 let item = array[index] // EXC_BAD_INSTRUCTION でクラッシュ
そのため、要素にアクセスする前に、インデックスのチェックを行う方法があります。
let array = ["A", "B", "C"] let index = 3 guard array.indices.contains(index) else { // インデックスの範囲外なら、nilを返す return nil } let item = array[index] // 実行されない }
面倒なのでついついサボって省略してしまい、後でクラッシュすることがあります。
実装
そこで、安全にアクセスできる”[safe: ]”を追加して、”[]”の代わりに使います。
これはインデックスが範囲外の場合、nilを返してくれます。
“[]”(subscript(Self.Index))はCollectionプロトコルのメソッドなので、同様にCollectionプロトコルに追加します。
extension Collection { subscript (safe index: Index) -> Element? { return indices.contains(index) ? self[index] : nil } }
用例
“[]”の代わりに”[safe: ]”を使うと、インデックスが範囲外でもクラッシュせずにnilを返します。
let array = ["A", "B", "C"] let index = 3 let item = array[safe: index] // nilを返す
注意
ちなみに配列の要素にnilが入っていた場合、戻り値の型は”Type??”になります。
let array = ["A", "B", "C", nil] let index = 3 let item = array[safe: index] // item : String??
itemに対する最初のunwrapでnilならインデックスが範囲外、2度目のunwrapでnilなら要素がnilです。
guard let firstUnwrappedItem = item else { return nil // インデックスが範囲外 } guard let secondUnwrappedItem = firstUnwrappedItem else { return nil // 要素がnil }
追記
「そもそも範囲外のインデックスにアクセスするのはバグなのだから、nilを返すよりクラッシュさせてしまうべき」との意見もあるのですが、ユーザーにとってはアプリがクラッシュするのは最悪の事態なので、クラッシュするよりも動かない(nilチェックでリターンさせているとほとんどこの動きになります)方がはるかに良いと考えてます。
これは組込み系でよくある、フェイルセーフの考え方が主軸になっています。
参考
xcode – Safe (bounds-checked) array lookup in Swift, through optional bindings? – Stack Overflow