Reputation: 2066
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?
Upvotes: 55
Views: 57941
Reputation: 3601
I know most people will go for the top-voted answers but let me post my solution and explain why I did so:
public extension Collection {
/**
Checks the `index` and returns a value if valid.
- Throws: `GeneralError.illegalArguent`
*/
func get(index: Index) throws -> Element {
// Note: GeneralError is a custom error type
guard indices.contains(index) else { throw GeneralError.illegalArgument }
return self[index]
}
}
We say that the force unwrap operator is "safe" because we can fully describe its behavior for all possible inputs, including input that doesn't satisfy its requirements.
For situations like these, Swift API tends to use term "checked" as in CheckedContinuation. So if I were to go with the subscript
solution, I would make it so that it looks like: someList[checked: index]
.
For instance, you can search for an element in an Array
with a predicate and get nothing back because no element matched. This is a legitimate reason to use optionals. However, if a routine cannot continue because a bad input was given, that should result in throwing an error, not an optional return value.
Swift doesn't support throwing subscripts, so I had to resort to using a method.
Upvotes: 0
Reputation: 6047
You can check the array index within the bounds, using below code.
guard array.indices.contains(index) else { return }
Upvotes: 2
Reputation: 2948
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
}
}
Upvotes: 2
Reputation: 5974
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
}
}
Index out of range
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
}
Upvotes: 77
Reputation: 35402
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 }
Upvotes: 31
Reputation: 121
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)
}
}
Upvotes: 1
Reputation: 118761
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.)
Upvotes: 34