52

I would really like to use a more simple classic try catch block in my Swift code but I can't find anything that does it.

I just need:

try {
// some code that causes a crash.
}
catch {
// okay well that crashed, so lets ignore this block and move on.
}  

Here's my dilema, when the TableView is reloaded with new data, some information is still siting in RAM which calls didEndDisplayingCell on a tableView with a freshly empty datasource to crash.

So I frequently thrown the exception Index out of range

I've tried this:

func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {

    do {
        let imageMessageBody = msgSections[indexPath.section].msg[indexPath.row] as? ImageMessageBody
        let cell = tableView.dequeueReusableCellWithIdentifier("ImageUploadCell", forIndexPath: indexPath) as! ImageCell
        cell.willEndDisplayingCell()
    } catch {
        print("Swift try catch is confusing...")
    }
}

I've also tried this:

func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
    print(indexPath.section)
    print(indexPath.row)

    if msgSections.count != 0 {
        if let msg = msgSections[indexPath.section].msg[indexPath.row] as? ImageMessageBody {
            let cell = tableView.dequeueReusableCellWithIdentifier("ImageUploadCell", forIndexPath: indexPath) as! ImageCell
            cell.willEndDisplayingCell()
        }
    }
}

This is a very low priority block of code, and I have wasted a lot of time with trial and error figuring out which error handler built into swift works for what seems to be extremely unique situations when I have tons of scenarios just like this one where the code can crash and it will not have any effect on the user experience.

In short, I don't need anything fancy but Swift seems to have extremely specific error handlers that differ based on whether I'm getting a value from a functions return value or getting a value from an array's index which may not exist.

Is there a simple try catch on Swift like every other popular programming language?

jtbandes
  • 115,675
  • 35
  • 233
  • 266
Matt Andrzejczuk
  • 2,001
  • 9
  • 36
  • 51
  • https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html – Sash Sinha May 14 '16 at 05:13
  • 6
    It seems you are trying to fix the wrong problem. You should be solving the problem of why `didEndDisplayCell` is being called on old data after you've reloaded the table view. That doesn't make any sense. Why would that happen? Are you updating the table view's data on a background thread? That would be one possible issue. – rmaddy May 14 '16 at 05:19
  • 3
    Generally **never** call delegate methods containing `did`, `will` and `should` by yourself. They are called by the framework. – vadian May 14 '16 at 06:11
  • 1
    you have to handle this by checking count of array before acessing by index. – Elangovan May 14 '16 at 07:10
  • Have you figured out why was this happening in your case? – jovanjovanovic Sep 26 '16 at 20:55

6 Answers6

75

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
}
Penkey Suresh
  • 5,816
  • 3
  • 36
  • 55
  • 7
    I wish this was the default behavior – vtcajones Nov 24 '16 at 04:42
  • not worked for me. Can you explain how this works and what is ~indices~ in this – Kumar Dec 17 '16 at 11:59
  • 4
    This introduces optionals to contexts when no optionals should be needed. The best solution is to let your app crash. Indexing out of bounds is a sign of programmer error. You shouldn't ignore that sign or try to swallow it with an optional. Just avoid it. – Sulthan Dec 17 '16 at 20:20
  • 6
    @Sulthan, Out of bound index may not be due to programming error. For instance you are dealing with dynamic data that you as a programmer have no control over. Language should safeguard the program from ever ever ending in a fetal run time error. It's a sign of bad language design and infancy of Swift compared to other languages. Hope this gets changed in future versions. – AlexVPerl Feb 02 '17 at 14:53
  • 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 '17 at 17:40
  • @ThomasTempelmann obviously because the array's maximum index is `2` and you try to access `5`. That returns `nil` and the app crashes due to the lack of the value requested – Mr. Xcoder Jun 07 '17 at 15:40
  • There might very well exist occasions where this is not a programming error. (=It worked for me.) Excellent. – Jonny Jul 05 '17 at 04:02
  • In Swift 5 `Generator.Element?` become `Iterator.Element?` – Joannes Nov 17 '20 at 10:43
  • @Joannes I've moved on from swift, but in case you were able to get a swift 5 version working, consider editing this existing answer or add a new one. – Penkey Suresh Nov 17 '20 at 18:55
34

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.)

jtbandes
  • 115,675
  • 35
  • 233
  • 266
  • In what circumstances could the following cause an index out of error then? `for i in 0...magnitudes.count-1 { barHeight = magnitudes[i]*maxHeight; }` - I'm getting this randomly in my app, and the magnitudes array isn't being altered in any other threads (that I can see). I also check that the count is more than 0 before this loop, too. – JCutting8 Aug 22 '21 at 02:57
  • I'd recommend you ask a new question: https://stackoverflow.com/questions/ask – jtbandes Aug 22 '21 at 05:02
  • Fair enough. After doing some logging, I found the count value jumps from the expected value (which is only 16), to some obscure number in the billions. I'm thinking its a memory issue so I'll probably just keep at it. Just thought there might be some obvious causes for the count to change without the developer initiating it. – JCutting8 Aug 22 '21 at 08:26
31

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 }
Alessandro Ornano
  • 34,887
  • 11
  • 106
  • 133
2

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
    }
}
Joannes
  • 2,569
  • 3
  • 17
  • 39
2

You can check the array index within the bounds, using below code.

guard array.indices.contains(index) else { return }
SiKing
  • 10,003
  • 10
  • 39
  • 90
Nathan Day
  • 5,981
  • 2
  • 24
  • 40
1

You can try a different approach to this. Will surely work!

if msgSections != nil {
    for msg in msgSections[indexPath.section] {
        if msgSections[indexPath.section].index(of: msg) == indexPath.row {
            (Code)
        }
}