How do I catch "Index out of range" in Swift?
Swift 4:
extension Collection where Indices.Iterator.Element == Index {
subscript (exist index: Index) -> Iterator.Element? {
return indices.contains(index) ? self[index] : nil
}
}
Usage:
var index :Int = 6 // or whatever number you need
if let _ = myArray[exist: index] {
// do stuff
}
or
var index :Int = 6 // or whatever number you need
guard let _ = myArray[exist: index] else { return }
Swift 5
This answer in Swift 5 become:
extension Collection where Indices.Iterator.Element == Index {
subscript (safe index: Index) -> Iterator.Element? {
return indices.contains(index) ? self[index] : nil
}
}
Otherwise from Swift 4 there's the ability to have clauses on associated types and it can become:
extension Collection {
subscript (safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
As suggested in comments and other answers it is better to avoid this kind of situations. However, in some cases you might want to check if an item exists in an array and if it does safely return it. For this you can use the below Array extension for safely returning an array item.
Swift 5
extension Collection where Indices.Iterator.Element == Index {
subscript (safe index: Index) -> Iterator.Element? {
return indices.contains(index) ? self[index] : nil
}
}
Swift 4
extension Collection where Indices.Iterator.Element == Index {
subscript (safe index: Index) -> Iterator.Element? {
return indices.contains(index) ? self[index] : nil
}
}
Swift 3
extension Collection where Indices.Iterator.Element == Index {
subscript (safe index: Index) -> Generator.Element? {
return indices.contains(index) ? self[index] : nil
}
}
Swift 2
extension Array {
subscript (safe index: Int) -> Element? {
return indices ~= index ? self[index] : nil
}
}
- This way you'll never hit
Index out of range
- You'll have to check if the item is
nil
refer this question for more
Trying the Swift3 code in a Playground in Xcode 8.3.2 still leads to a "crash" when I do let ar = [1,3,4], then let v = ar[5]. Why? – Thomas Tempelmann May 17 at 17:40
You have to use our customized subscript so instead of let v = ar[5]
, it wll be let v = ar[safe: 5]
.
Default getting value from array.
let boo = foo[index]
Add use the customized subscript.
let boo = fee[safe: index]
// And we can warp the result using guard to keep the code going without throwing the exception.
guard let boo = foo[safe: index] else {
return
}
Swift's Error Handling (do
/try
/catch
) is not the solution to runtime exceptions like "index out of range".
A runtime exception (you might also see these called trap, fatal error, assertion failure, etc.) is a sign of programmer error. Except in -Ounchecked
builds, Swift usually guarantees that these will crash your program, rather than continuing to execute in a bad/undefined state. These sorts of crashes can arise from force-unwrapping with !
, implicit unwrapping, misuse of unowned
references, integer operations/conversions which overflow, fatalError()
s and precondition()
s and assert()
s, etc. (And, unfortunately, Objective-C exceptions.)
The solution is to simply avoid these situations. In your case, check the bounds of the array:
if indexPath.section < msgSections.count && indexPath.row < msgSections[indexPath.section].msg.count {
let msg = msgSections[indexPath.section].msg[indexPath.row]
// ...
}
(Or, as rmaddy says in comments — investigate why this problem is occurring! It really shouldn't happen at all.)