I thought I was doing it correctly.
let realm = try! Realm()
do {
try realm.write {
realm.add(myObject)
}
} catch {
print("something went wrong!")
}
But I'm still getting a crash instead of that print statement. I'm not interested in avoiding the exception (in this case I caused it deliberately by adding an object with an existing primary key) but I want to be able to catch it and prevent a crash no matter what. Is this possible, and if so, how?
Realm Swift throws Objective-C exceptions only for things that are considered to be programmer error. These exceptions are not intended to be caught and handled at runtime as they're indicative of an error in the program that must be fixed.
Related
I found some crashes in my app despite of try? construction. The firebase crashlytics logged
Fatal Exception: NSInvalidArgumentException
Invalid number value (infinite) in JSON write
Here is a test example
let avgSpeed = 0.1 / 0
print(avgSpeed)
let data = ["average_speed" : avgSpeed]
if let body = try? JSONSerialization.data(withJSONObject: [data]) {
print("success")
} else {
print("unable to make body for call")
}
Why did that happen?
iOS 13, swift 4
try? does not catch exceptions. It catches thrown errors. Those are different things in Swift. Exceptions are at the Objective-C level and cannot be caught by Swift at all (they can't be safely caught in ObjC in most cases either, but that's a different discussion).
The solution in this case is to use JSONEncoder rather than JSONSerialization. JSONEncoder is a pure-Swift system. JSONSerialization is bridged from ObjC.
let body = try? JSONEncoder().encode([data])
See Handling Errors for more information:
Error handling in Swift resembles exception handling in other languages, with the use of the try, catch and throw keywords. Unlike exception handling in many languages—including Objective-C—error handling in Swift does not involve unwinding the call stack, a process that can be computationally expensive. As such, the performance characteristics of a throw statement are comparable to those of a return statement.
If you want to use JSONSerialization, it's important to recognize that it is a programming error to call it this way. The exception is intended to crash the program (even in ObjC). The correct way to write this code is:
if JSONSerialization.isValidJSONObject([data]), // <=== first, check it is valid
let body = try? JSONSerialization.data(withJSONObject: [data]) {
print("success")
} else {
print("unable to make body for call")
}
See the docs for more information:
If obj will not produce valid JSON, an exception is thrown. This exception is thrown prior to parsing and represents a programming error, not an internal error. You should check whether the input will produce valid JSON before calling this method by using isValidJSONObject(_:).
The thrown error from JSONSerialization is only to indicate an internal error in the serializer, not an attempt to encode an invalid object:
error
If an internal error occurs, upon return contains an NSError object with code NSPropertyListWriteInvalidError that describes the problem.
I'm trying to create a DB Transaction following a safe path. So I'd like to do something using "do catch" statement, I'm wondering something like that:
do {
try? db.endTransaction()
}catch{ //THIS WHOLE CODE CRASHES
print(error.localizedDescription)
db.rollbackTransaction()
}
However, something is going wrong using this syntaxe. What am I doing wrong? Is this the best way to do what I want?
Inside a do block, you should remove the ? on try because the whole point of do is to catch errors that are thrown by things called with try. Once you do that, if your endTransaction() throws an exception, it should be caught by your catch block.
I have some code
captureSession = AVCaptureSession()
captureSession!.sessionPreset = AVCaptureSessionPresetPhoto
let backCamera = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo) etc...
Of course, this correctly does not work on simulator. No problem.
If you do run it on a simulator, it correctly crashes right here
captureSession!.sessionPreset = AVCaptureSessionPresetPhoto
like this
As a curiosity, how would you "catch" that crash?
If I try to "try" it in different ways,
try captureSession!.sessionPreset = AVCaptureSessionPresetPhoto
I only get...
/Users/jpm/Desktop/development/-/classes/CameraPlane.swift:67:3: No calls to throwing functions occur within 'try' expression
How is it you wrap and catch that type of call?
Just BTW for anyone specifically dealing with this annoyance in the Camera,
func cameraBegin()
{
captureSession = AVCaptureSession()
if ( AVCaptureDevice.devices().count == 0 )
{
print("Running on simulator.")
return
}
...
try can only be used when calling a function that throws. These functions are explicitly marked with throws.
If you want to unwrap an optional safely (which I guess is what you want to achieve), you can use guard.
var number: Int?
guard let unwrappedNumber = number else {
return
}
print("number: \(unwrappedNumber)")
Unfortunately, EXC_BAD_ACCESS means it's too late to catch anything because the program will be stopped.
You can only "catch" exceptions that are provided by the language / runtime you use. That would be error objects in swift (or OjbC methods with an NSError** parameter), obj-c Exceptions you could catch with #try/#catch blocks (but they disrupt memory management and shouldn't be used for runtime error handling) or C++ exceptions.
So far, swift / objc don't have the concept of a NullReferenceException for a number of reasons, but EXC_BAD_ACCESS also occur when accessing already freed / invalid memory, which was a common programming error before ObjC ARC and swift. So only guarding against accessing the "0" memory address wouldn't help in those situations. Any handling of this would have to deal with all three of the above mentioned error/exception handling mechanisms and potentially corrupt memory management (unlike Java / .NET, there is no garbage collector that cleans up all the objects left over after performing an unexpected non-local return).
So the handling of the "bad" memory access is not protected by anything and will result in an immediate crash with no reasonable way to recover. So the only thing you can do is to perform runtime checks if the operation you are about to do is valid and use the "bang" (!) operator with caution.
Here's my Swift 2.1 code snippet. The error that's occurring is shown in the comments at the point where the error appears.
The error shows up in the debugging panel, and the app crashes. The app never prints the line in the catch, nor does it gracefully return as expected.
let audioFileURL = receivedAudio.filePathURL
guard let audioFile = try? AVAudioFile(forReading: audioFileURL) else {
print("file setup failed")
return
}
let audioFileFrameCount = AVAudioFrameCount(audioFile.length)
audioFileBuffer = AVAudioPCMBuffer(PCMFormat: audioFile.fileFormat, frameCapacity: audioFileFrameCount)
do {
// ERROR: AVAudioFile.mm:263: -[AVAudioFile readIntoBuffer:frameCount:error:]: error -50
// Terminating app due to uncaught exception 'com.apple.coreaudio.avfaudio', reason: 'error -50'
// -50 = Core Audio: bad param
try audioFile.readIntoBuffer(audioFileBuffer)
}
catch {
print("unable to load sound file into buffer")
return
}
From everything I've seen, my do/try/catch format should be correct.
audioFile.readIntoBuffer returns void and has the keyword throws.
Yet, the catch is never executed.
What am I missing?
UPDATE: From Apple's documentation on AVAudioFile
For:
func readIntoBuffer(_ buffer: AVAudioPCMBuffer) throws
Under Discussion:
HANDLING ERRORS IN SWIFT:
In Swift, this API is imported as an initializer and is marked with the throws keyword to indicate that it throws an error in cases of failure.
You call this method in a try expression and handle any errors in the catch clauses of a do statement, as described in Error Handling in The Swift Programming Language (Swift 2.1) and Error Handling in Using Swift with Cocoa and Objective-C (Swift 2.1).
From The Swift Programming Language (Swift 2.1): Error Handline
NOTE
Error handling in Swift resembles exception handling in other languages, with the use of the try, catch and throw keywords. Unlike exception handling in many languages—including Objective-C—error handling in Swift does not involve unwinding the call stack, a process that can be computationally expensive. As such, the performance characteristics of a throw statement are comparable to those of a return statement.
And, finally, from the same document:
Handling Errors Using Do-Catch
You use a do-catch statement to handle errors by running a block of code. If an error is thrown by the code in the do clause, it is matched against the catch clauses to determine which one of them can handle the error.
I don't have to write and throw my own errors/exceptions for them to be caught. I should be able to catch Swift's exceptions as well.
The do - catch combination is fine. This issue is simply one that cannot be caught - and therefore never makes it to the catch block.
If the issue were catchable (defined and handled via Swift's throws functionality), the catch block would've been executed.
Some semantics: there is a long-standing argument about the differences between the terms error and exception.
Some developers consider the two terms to be distinct. In this case, the term error represents an issue that was designed to be handled. Swift's throws action would fit here. In this case, a do - catch combination would allow the issue to be caught.
An exception, for these developers, represents an unexpected, usually fatal, issue that cannot be caught and handled. (Generally, even if you could catch it, you would not be able to handle it.)
Others consider the two terms to be equivalent and interchangeable, regardless of whether the issue in question can be caught or not. (Apple's documentation seems to follow this philosophy.)
(Updated to focus on the answer rather than the semantics.)
catch will only catch the errors that are explicitly thrown. It will never catch exceptions.
What you seem to have here is an exception happening in the AVAudioFile SDK, not a Swift error, so it's not caught:
ERROR: AVAudioFile.mm:263: -[AVAudioFile readIntoBuffer:frameCount:error:]: error -50
Terminating app due to uncaught exception 'com.apple.coreaudio.avfaudio', reason: 'error -50'
-50 = Core Audio: bad param
In the context of Swift, "error" means an error thrown by a function, nothing else. The function being yours or not does not matter.
Exceptions in Swift are not caught ever. It's not the same as in Java at all, for example. In Swift, error != exception, they are two very different things.
I understand your opinion that "It should work for both" but it's simply not the case. You can see this as a semantic situation with using the keyword "catch" if you want, because it's the same keyword as other languages but behaves very differently; it resembles, but it's not the same.
As for your exception with AVAudioFile, I don't have a solution - maybe it's a bug in this SDK? Or it's not yet properly bind to Swift and the throwing system. In this case, and if nobody else has a solution, don't hesitate to report the bug to Apple.
see this example
struct E: ErrorType{}
func foo(i: Int) throws {
if i == 0 {
throw E()
}
print(10 / (i - 1))
}
do {
//try foo(1) // if you uncomment this line, the execution
// will crash, even though the function is declared
// as throwing and you use proper calling style (do / try / catch pattern)
try foo(0)
} catch {
print("error: ", error) // error: E()
}
In Swift, NSKeyedUnarchiver.unarchiveObjectWithData(data) will throw an exception if data can't be unarchived.
There are some situations where we have no guarantee if that the data is not corrupted, such as when reading from a file.
I am not aware of a try/catch mechanism in Swift, nor that I know of a method like canUnarchive that would help prevent the exception.
Besides implementing the try/catch in Obj-C, is there a pure Swift solution to this problem?
Because unarchiveObjectWithData() doesn't throw its exception, there is currently no way to catch it in Swift (as of writing). The iOS 9 SDK has added a new NSKeyedUnarchiver method decodeTopLevelObject() which now throws an error. You can catch this with the do, try, catch control flow.
do {
let result = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(NSData(...))
} catch {
print(error)
}