Most to all of the classes in Objective-C returns nil if passed nil or some error, but NSURL throws an exception. Specifically, the method [NSURL fileURLWithPath].
It is documented:
"Passing nil for this parameter produces an exception."
But, can anyone explain Apple would throw an exception instead of returning nil?
It is calling a method on nil that is allowed. Many methods can throw exceptions if given an invalid argument. For example see NSMutableArray's addObject:.
Here is Apple's docs or error handling:
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/ErrorHandling/ErrorHandling.html
exceptions are used solely for programmer errors
Related
I have an iOS application written in Swift 2 in Xcode 8.2.1, that's built for iOS 10.2.
I've had a number of crash reports from TestFlight and despite symbolication, none of the crash logs show any program state besides the stack-traces (no argument values, no locals, no heap objects, etc).
...but inside those functions I can see code which is likely to fail (e.g. a forced unwrap) but the crash log isn't telling me where or why it's failing.
When debugging in Xcode, I can use fatalError(message: String) where I can put my own message like "functionFoo returned nil" or "variable bar == \"" + bar + "\"", except when deployed using TestFlight or the App Store the fatalError will be hit and the program terminates, but the message value is not saved to the crash log, making it pointless.
In other environments, like C#/.NET and Java I can simply throw new SomeExceptionType("my message") and all information is available in whatever global catch(Exception) handler I have.
How can I achieve the same goal in iOS / Swift?
Swift does support error handling. You can create your own error type by confirming to Error protocol or use existing error types and then throw an error by invoking throw error.
But Swift forces you add error handling to any code that can throw an error. There are multiple way you can handle error in swift.
Apply throws keyword to your function, this indicates that the function can throw an error when invoked and the error should be handled by the caller.
func canThrowErrors() throws -> String
When invoking methods with throws keyword you have to add try keyword at the beginning of the invocation. All these try invocations should be handled either by applying throws to method to just propagate the errors or wrapping inside a do-catch block:
do {
try canThrowErrors()
try canThrowOtherErrors()
} catch is SpecificError {
// handling only specific error type
} catch let error as SpecificError {
// catches only specific error for type
} catch {
// catches all errors
}
Additionally you can use try? and try! for throwing function invocation to disable error propagation and retrieve optional result that returns nil in case of error and runtime assertions respectively.
By forcing you to handle all the errors at compile time swift avoids any undefined runtime behavior and debugging nightmare.
I would suggest to use fatalError or any other runtime assertion only if scenarios when there is no way to recover from a state without crashing the app. Unfortunately, there is no way to handle errors from fatalError as its use is only reserved for such scenarios only. Also, in your crashlog you will only get the line number that caused the crash to get additional info for the cause of crash I would suggest to use custom logging or analytics.
In my custom framework, I have a method like the one shown below which fetches value from dictionary and converts it into BOOL and returns the boolean value.
- (BOOL)getBoolValueForKey:(NSString *)key;
What if the caller of this method passes a key that does not exist. Should I throw a custom NSException saying key does not exist(but throwing exception is not recommended in objective c) or add NSError parameter to this method as shown below?
- (BOOL)getBoolValueForKey:(NSString *)key error:(NSError **)error;
If I use NSError, I will have to return 'NO' which will be misleading since 'NO' can be a valid value of any valid key.
The API for this is long-established by NSUserDefaults, and should be your starting point for designing your API:
- (BOOL)boolForKey:(NSString *)defaultName;
If a boolean value is associated with defaultName in the user defaults, that value is returned. Otherwise, NO is returned.
You should avoid creating a different API for fetching bools from a keystore unless you have a strong reason. In most ObjC interfaces, fetching a non-exixtant key returns nil and nil is interpreted as NO in a boolean context.
Traditionally, if one wants to distinguish between NO and nil, then call objectForKey to retrieve the NSNumber and check for nil. Again, this is behavior for many Cocoa key stores and shouldn't be changed lightly.
However, it is possible that there is a strong reason to violate this expected pattern (in which case you should definitely note it carefully in the docs, because it is surprising). In that case, there are several well established patterns.
First, you can consider fetching an unknown key to be a programming error and you should throw an exception with the expectation that the program will soon crash because of this. It is very unusual (and unexpected) to create new kinds of exceptions for this. You should raise NSInvalidArgumentException which exists exactly for this problem.
Second, you can distinguish between nil and NO by correctly using a get method. Your method begins with get, but it shouldn't. get means "returns by reference" in Cocoa, and you can use it that way. Something like this:
- (BOOL)getBool:(BOOL *)value forKey:(NSString *)key {
id result = self.values[key];
if (result) {
if (value) {
// NOTE: This throws an exception if result exists, but does not respond to
// boolValue. That's intentional, but you could also check for that and return
// NO in that case instead.
*value = [result boolValue];
}
return YES;
}
return NO;
}
This takes a pointer to a bool and fills it in if the value is available, and returns YES. If the value is not available, then it returns NO.
There is no reason to involve NSError. That adds complexity without providing any value here. Even if you are considering Swift bridging, I wouldn't use NSError here to get throws. Instead, you should write a simple Swift wrapper around this method that returns Bool?. That's a much more powerful approach and simpler to use on the Swift side.
If you wish to communicate passing a non-existent key as a programmer error, i.e. something that should actually never occur during runtime because for instance something upstream should have taken care of that possibility, then an assertion failure or NSException is the way to do it. Quoting Apple's documentation from the Exception Programming Guide:
You should reserve the use of exceptions for programming or unexpected runtime errors such as out-of-bounds collection access, attempts to mutate immutable objects, sending an invalid message, and losing the connection to the window server. You usually take care of these sorts of errors with exceptions when an application is being created rather than at runtime.
If you wish to communicate a runtime error from which the program can recover / can continue executing, then adding an error pointer is the way to do it.
In principle it is fine to use BOOL as the return type there even if there is a non-critical error case. There are however corner cases with this in case you intend to interface with this code from Swift:
If you are accessing this API via Swift, NO always implies that an error is thrown, even if in your Objective-C method implementation you do did not populate the error pointer, i.e. you would need a do / catch and handle specifically of a nil error.
The opposite actually is also valid, i.e. it is possible to throw an error in the success case (NSXMLDocument for instance does this to communicate non-critical validation errors). There is to my knowledge no way to communicate this non-critical error information to Swift.
If you do intend to use this API from Swift, I would perhaps box the BOOL to a nullable NSNumber (at which case the error case would be nil, and the successful NO case would be an NSNumber with NO wrapped in it).
I should note, for the specific case of a potentially failable setter, there are strong conventions that you should follow, as noted in one of the other answers.
You pinpoint the major weakness in Apples error handling approach.
We are dealing with those situations by guaranteeing that the NSError is nil in success cases, so you actually check the error:
if (error) {
// ... problem
// handle error and/ or return
}
As this contradicts Apples error handle, where an Error is never guaranteed to be nil, but is guaranteed to be not nil in failure cases, affected methods have to be well documented to the clients know about this special behaviour.
This is not a nice solution, but the best I know.
(This is one of the nasty things we do not have to deal with any more in swift)
If You want all these
Distinguish between failure and success cases
Work with the bool value only if it is a success
In case of failure, caller mistakenly does not think return value is the value of the key
I suggest to make a block based implementation. You'll have a successBlock and errorBlock to clearly separate.
Caller will call the method like this
[self getBoolValueForKey:#"key" withSuccessBlock:^(BOOL value) {
[self workWithKeyValue:value];
} andFailureBlock:^(NSError *error) {
NSLog(#"error: %#", error.localizedFailureReason);
}];
and the implementation:
- (void)getBoolValueForKey:(NSString *)key withSuccessBlock:(void (^)(BOOL value))success andFailureBlock:(void (^)(NSError *error))failure {
BOOL errorOccurred = ...
if (errorOccurred) {
// userInfo will change
// if there are multiple failure conditions to distinguish between
NSDictionary *userInfo = #{
NSLocalizedDescriptionKey: NSLocalizedString(#"Operation was unsuccessful.", nil),
NSLocalizedFailureReasonErrorKey: NSLocalizedString(#"The operation timed out.", nil),
NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(#"Have you tried turning it off and on again?", nil)
};
NSError *error = [NSError errorWithDomain:#"domain" code:999 userInfo:userInfo];
failure(error);
return;
}
BOOL boolValue = ...
success(boolValue);
}
We use this
- (id) safeObjectForKey:(NSString*)key {
id retVal = nil;
if ([self objectForKey:key] != nil) {
retVal = [self objectForKey:key];
} else {
ALog(#"*** Missing key exception prevented by safeObjectForKey");
}
return retVal;
}
Header file NSDictionary+OurExtensions.h
#import <Foundation/Foundation.h>
#interface NSDictionary (OurExtensions)
- (id) safeObjectForKey:(NSString*)key;
#end
In this case, I would prefer returning NSInteger with returning 0, 1 and NSNotFound if caller passes key that doesn't exist.
From the nature of this method, It should be caller judgement to handle NSNorFound. As I can see, returning error is not very encouraging to user from the method's name.
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()
}
I've run into this silly behaviour in swift where force-unwrapping an optional does not propagate.
From the documentation:
Trying to use ! to access a non-existent optional value triggers a runtime error. Always make sure that an optional contains a non-nil value before using ! to force-unwrap its value.
To reproduce:
func foo(bar:String?) throws{
print(bar!);
}
And
try foo(nil);
This does not seem logical or consistent to me and I can't find any documentation on this subject.
Is this by design?
From the documentation:
Error Handling
Error handling is the process of responding to and recovering from
error conditions in your program. Swift provides first-class support
for throwing, catching, propagating, and manipulating recoverable
errors at runtime.
...
Representing and Throwing Errors
In Swift, errors are represented by values of types that conform to
the ErrorType protocol. This empty protocol indicates that a type can
be used for error handling.
(Note: ErrorType has been renamed to Error in Swift 3)
So with try/catch you handle Swift errors (values of types that conform to the ErrorType protocol) which are thrown.
This is completely unrelated to runtime errors and runtime exceptions
(and also unrelated to NSException from the Foundation library).
Note that the Swift documentation on error handling does not even use the
word "exception", with the only exception (!) in (emphasis mine) in:
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.
The unwrapping of optionals which are nil does not throw a
Swift error (which could be propagated) and cannot be handled with
try.
You have to use the well-known techniques like
optional binding, optional chaining, checking against nil etc.
this 'self explanatory' example can help you to see the difference between raising an runtime exception and throwing an error E conforming to ErrorType protocol.
struct E: ErrorType{}
func foo(bar:String?) throws {
if let error = bar where error == "error" {
throw E()
}
print(bar, "is valid parameter, but don't try to access bar.characters, it crash your code! (if bar == nil)")
// here is everything OK
let bar = bar!
// but here it crash!!
_ = bar.characters
}
do {
try foo("error")
// next line is not accessible here ...
try foo(nil)
} catch {
print("\"error\" as parameter of foo() throws an ERROR!")
}
do {
try foo(nil) // fatal error: unexpectedly found nil while unwrapping an Optional value
} catch {
}
it prints
"error" as parameter of foo() throws an ERROR!
nil is valid parameter, but don't try to access bar.characters, it crash your code! (if bar == nil)
fatal error: unexpectedly found nil while unwrapping an Optional value
raising an runtime exception is fatal error in your code.
When a method returns a BOOL and Error at the same time, Is it enough to check for BOOL status or should we add the additional condition for Error as well?
For example, Following method returns a BOOL and error if any.
-(BOOL)canEvaluatePolicy:(LAPolicy)policy error:(NSError * __autoreleasing *)error;
Now should I write
BOOL biometricsAvailable = [context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error];
available = (error == nil && biometricsAvailable);
or
BOOL biometricsAvailable = [context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error];
is enough?
It is clearly described in the documentation here; https://developer.apple.com/library/prerelease/ios/documentation/LocalAuthentication/Reference/LAContext_Class/index.html#//apple_ref/occ/instm/LAContext/canEvaluatePolicy:error:,
Return Value
true if the policy can be evaluated, false otherwise.
Parameters
policy
The policy to evaluate.
error
On input, a pointer to an error object. If an error occurs, this pointer is set to an actual error object containing the error information. You may specify nil for this parameter if you do not want the error information.
So, this means that the Boolean return value tells you if the evaluation was successful. And in case that fails, your error object will be set, which will have a description about the failure.
No, checking the return value should be enough. But when NO is returned, you can have a look at the error variable to see why.
Apple have stated that you should check the return value of the method and only when this is NO or nil can you check the error,
since the SDK could put some weird value in the error variable.
See the document Programming with Objective-C - Dealing with Errors
Define "enough". For what?
The contract of the convention is that if there is a problem, NO should be returned and if you passed in an NSError pointer it'll be populated. If you want to do something with the error, you have to check it, but the convention says that there'll never be a case where the error is provided but YES is returned (if YES is returned the pointer shouldn't even be touched), or where NO is returned and there's no error. This convention is everywhere in Cocoa and has been stable for decades, and since Swift just based their error handling on this model, I think this is even less likely to change.
Both cases are different. It Depends on your requirement.
If you use only BOOL then you only get the status of the request. Whether it is failed or succeed and based on that you can perform task.But you won't be able to know what is the error.
To know what exactly the error is you should go with with first method. If you want to know.