CFNetwork error handling in Swift - objective-c

I have a block of objective-c code that responsible for handling HTTP errors by checking the NSError code error. The code sends the error message back to a delegate (unless it's a error code that the app ignores, such as cancelled requests)
failure:^(NSURLSessionTask *task, NSError *error, id responseObject) {
NSString *errorMessage = [self getErrorMessage:responseObject withError:error];
if (error.code!=kCFURLErrorCancelled &&
error.code!=kCFURLErrorCannotFindHost &&
error.code!=kCFURLErrorCannotConnectToHost &&
error.code!=kCFURLErrorNetworkConnectionLost &&
error.code!=kCFURLErrorDNSLookupFailed &&
error.code!=kCFURLErrorNotConnectedToInternet &&
error.code!=kCFURLErrorTimedOut) {
if ([self.delegate respondsToSelector:#selector(didFailed:)]) {
[self.delegate didFailed:errorMessage];
}
}
if (completionBlock != nil) completionBlock(NO);
}];
I have several questions / issues related to this code block.
Is is sufficient to just check the error code? The framework I'm using might return a different type of errors and I'm not sure those error codes are unique.
How would I go and write the same code in Swift? I did find the error codes definitions under CFNetworkErrors enum. However, the error.code & the value from the CFNetworkErrors enum cannot be compared directly, as they have different types. Is it possible to cast error.code to a CFNetworkErrors and then compare the error codes?
Can I safely switch to use the NSURL errors such as NSURLErrorCancelled? Is there a 1-to-1 mapping between the CFNetworks errors and NSURL errors?

It is probably not sufficient to check the error code. You almost certainly need to check the HTTP status code as well, unless the delegate method you're calling already does that. An NSError typically tells you about transport-level failures, whereas the status code tells you about server-side failures, such as a file not existing.
No idea. That question is mostly unrelated to networking, so you should probably ask a separate question for it, and tag it as a Swift question. :-)
Yes, you should be using the Foundation codes. The values should always be identical, but there are likely to be more Foundation error codes than CF codes going forward, as not all of the Foundation-level functionality is exposed publicly at the CF layer.

Related

XCTest Completion Handler Assertion Retain Cycle

I have been writing Xcode tests using the XCTest framework for a while, mostly async tests of the getters of a service with completion handlers of the following format with no issues:
XCTestExpectation *promise = [self expectationWithDescription:#"Get Something should succeed"];
[self.myService getSomethingOnCompletion:^(NSError * _Nullable error) {
XCTAssertNil(error, #"Error should be nil");
[promise fulfill];
}];
[self waitForExpectations:#[promise] timeout:2.0];
Suddenly today I go to write my first async setter test of the below format, but get warnings on the XCTAssert...() statement within the block saying:
Capturing 'self' strongly in this block is likely to lead to a retain cycle
XCTestExpectation *promise = [self expectationWithDescription:#"Set Something should succeed"];
[self.myService setSomething:#"..." onCompletion:^(NSError * _Nullable error) {
XCTAssertNil(error, #"Error should be nil");
[promise fulfill];
}];
[self waitForExpectations:#[promise] timeout:2.0];
I've even gone to the lengths of commenting out the entire contents of setSomething: onCompletion: such that it does not do anything, clean and rebuild, yet the warning still persists.
I do not understand what self it is referring to, as the only thing going on inside the block is an XCTAssert...() and [XCTestExpectation fulfill]. Furthermore, I do not understand why none of the 30+ tests I've written of the first format have no warnings associated with them, but all 5+ I've written of the 2nd format do.
Any explanation on what's going on here and how I can resolve it would be appreciated.
(Using Xcode 10.0)
Edit 1:
The issue seems to be with the method name, setSomething: onCompletion:. Changing it to anything else, such as doSomething: onCompletion: removes the warning. I still don't know how/ why Xcode interprets the set command in such a fashion that it presents the warning, so any information would be appreciated.
Edit 2:
The following are the method signatures of setSomething and doSomething:
- (void)setSomething:(EnumType)type onCompletion:(SetSomethingCompletionHandler)completion;
- (void)doSomething:(EnumType)type onCompletion:(SetSomethingCompletionHandler)completion
Where SetSomethingCompletionHandler is defined as :
typedef void (^SetSomethingCompletionHandler)(NSError * _Nullable error);

How to mock class methods in objective c to use with TDD

I am using a third party framework to send data to a server. I am mocking that third party framework to isolate and test how my code interacts with it. This is to avoid to avoid waiting to get back data from the network while unit testing, and because I want to test the error handling code that I've written.
I am not using OCMock or anything like it, I am writing my own mock object by subclassing the 3rd party framework.
My method looks like this-
- (void)loginWithCredentials:(NSDictionary *)credentials
{
NSDictionary *credentials = [self credentials];
NSString *username = [credentials objectForKey:kUserCredintialUsername];
NSString *password = [credentials objectForKey:kUserCredintialPassword];
[ThirdPartyClass loginWithUsername:username
andPassword:password
block:^(ThirdPartyClass *user, NSError *error){
if (user) {
NSLog(#"We logged in");
}
else if (error) {
NSLog(#"%#", [error errorString]);
}
}];
}
What I'd like to do is to is to call loginWithUsername:andPassword:block: on my mock in my unit tests. The current method is, obviously, untestable because it doesn't follow "tell, don't ask" (I'm not telling loginWithCredentials: which class to call loginWithUser...block: on). The only way that I can think to solve this is to add a class variable to my instance or class or add an argument to loginWithCredentials: so I can substitute my mock in. The production code doesn't gain any clarity by doing so - it is already being written as an adapter to the 3rd party library. Instead, I would prefer to try to refactor the loginWithCredentials: method.
There is an HTTP stubbing library at https://github.com/AliSoftware/OHHTTPStubs. It works by catching outgoing requests and returning file data. It also allows rudimentary simulation of loading speeds for different types of networks.

Objective-C Framework Error Handling

I'm creating a framework for use by a Cocoa Application on 10.6 and later.
The purpose of the framework is to parse a text file.
Obviously, there are errors that could occur, such as file not found, permissions issues, etc.
What is the right way to handle errors within the framework and notify the host application?
My thoughts were:
Do nothing and let the host application catch any exceptions.
Have the host application register its first responder with the framework, catch any exceptions, convert them into NSError and pass them to the host app's responder chain.
Do either of those options make sense? Are there other options? What's the right way to handle this?
I have read the error and exception handling guides, but they don't cover this situation and only describe error handling within the application itself.
I would say the correct way is to use NSError directly yourself in all methods that can error. I have done this recently with a utility class I created, and it works very well. You then allow the application to decide what do to with the error (crash, log, something else) and the framework doesn't need to worry.
Here are the private class methods I used to create the error objects, allowing for underlying POSIX errors (errno etc.):
#pragma mark - Private Methods
- (NSError *)error:(NSString *)localizedDescription
code:(EZipFileError)code
underlyingError:(NSError *)underlyingError
{
NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary];
[errorDetail setValue:localizedDescription forKey:NSLocalizedDescriptionKey];
if (underlyingError != nil)
{
[errorDetail setValue:underlyingError forKey:NSUnderlyingErrorKey];
}
return [NSError errorWithDomain:#"MyErrorDomain"
code:(NSInteger)code
userInfo:errorDetail];
}
- (NSError *)error:(NSString *)localizedDescription
code:(EZipFileError)code
{
return [self error:localizedDescription
code:code
underlyingError:nil];
}
- (NSError *)error:(NSString *)localizedDescription
code:(EZipFileError)code
posixError:(int)posixError
{
NSMutableDictionary *underlyingErrorDetail = [NSMutableDictionary dictionary];
[underlyingErrorDetail setValue:[NSString stringWithUTF8String:strerror(posixError)]
forKey:NSLocalizedDescriptionKey];
NSError *underlyingError = [NSError errorWithDomain:NSPOSIXErrorDomain
code:posixError
userInfo:underlyingErrorDetail];
return [self error:localizedDescription
code:code
underlyingError:underlyingError];
}
Which is used as follows:
if (![self isOpen])
{
if (error != NULL)
{
*error = [self error:#"File is not open"
code:ErrorNotOpen];
}
return nil;
}
Here's an example that uses the underlying POSIX error version of the method:
filefp = fopen([filename UTF8String], "rb");
if (filefp == NULL)
{
if (error != NULL)
{
*error = [self error:#"Failed to open file"
code:ErrorOpenFileFailed
posixError:errno];
}
return NO;
}
Exceptions should be used only for terminal errors in Objective-C. More specifically, Cocoa and Cocoa Touch don't guarantee that exceptions thrown across their boundaries will come out the other side, so you shouldn't use exceptions for error handling.
The right way to report errors to the caller is via an NSError object. You'll notice that many Cocoa and Cocoa Touch methods include a NSError* parameter for exactly that purpose.

More Detailed Error From createFileAtPath?

Is there anyway to get more detailed error data back from "createFileAtPath" I was kind of expecting an NSError? Currently I am using the BOOL return value.
success = [fileMan createFileAtPath:fileOnDisk contents:dBuffer attributes:nil];
if(success == YES) NSLog(#"FileCreated");
else {
NSLog(#"ERROR: Failed to create file");
return 1;
}
gary
I agree... I'd love to have a function for this that accepts NSError!
Errors returned in this case are usually one of the POSIX errors declared in errno.h (errno is automatically included for you as part of the Cocoa or Foundation headers).
To see the error, use the strerror function from errno.h and reference the global errno integer, which is set by the low-level POSIX io functions when a problem occurs:
if (![fm createFileAtPath:#"/etc/foobar.txt" contents:data attributes:nil])
{
NSLog(#"Error was code: %d - message: %s", errno, strerror(errno));
}
// output will be: Error was code: 13 - message: Permission denied
The list of error code constants are listed in the in the Error Handling Programming Guide for Cocoa (in addition to the errno.h header itself).
You're not supposed to use that method; they forgot to put this in the main docs (!), but if you read the Apple header file you find this comment:
/* These methods are provided here for compatibility. The corresponding
methods on NSData which return NSErrors should be regarded as the
primary method of creating a file from an NSData or retrieving the
contents of a file as an NSData. */
So, instead, Apple expects you to use this (which, from testing, appears to function exactly the same - except that it's located in a bizarre class where you'd never think to look for it (I want to create an empty file ... so I have to ... instantiate a nil NSData object? What?) it has an NSError object):
https://developer.apple.com/library/ios/documentation/cocoa/reference/foundation/Classes/NSData_Class/Reference/Reference.html#//apple_ref/occ/instm/NSData/writeToFile:options:error:

throwing an exception in objective-c/cocoa

What's the best way to throw an exception in objective-c/cocoa?
I use [NSException raise:format:] as follows:
[NSException raise:#"Invalid foo value" format:#"foo of %d is invalid", foo];
A word of caution here. In Objective-C, unlike many similar languages, you generally should try to avoid using exceptions for common error situations that may occur in normal operation.
Apple's documentation for Obj-C 2.0 states the following: "Important: Exceptions are resource-intensive in Objective-C. You should not use exceptions for general flow-control, or simply to signify errors (such as a file not being accessible)"
Apple's conceptual Exception handling documentation explains the same, but with more words: "Important: 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. [.....] Instead of exceptions, error objects (NSError) and the Cocoa error-delivery mechanism are the recommended way to communicate expected errors in Cocoa applications."
The reasons for this is partly to adhere to programming idioms in Objective-C (using return values in simple cases and by-reference parameters (often the NSError class) in more complex cases), partly that throwing and catching exceptions is much more expensive and finally (and perpaps most importantly) that Objective-C exceptions are a thin wrapper around C's setjmp() and longjmp() functions, essentially messing up your careful memory handling, see this explanation.
#throw([NSException exceptionWith…])
Xcode recognizes #throw statements as function exit points, like return statements. Using the #throw syntax avoids erroneous "Control may reach end of non-void function" warnings that you may get from [NSException raise:…].
Also, #throw can be used to throw objects that are not of class NSException.
Regarding [NSException raise:format:]. For those coming from a Java background, you will recall that Java distinguishes between Exception and RuntimeException. Exception is a checked exception, and RuntimeException is unchecked. In particular, Java suggests using checked exceptions for "normal error conditions" and unchecked exceptions for "runtime errors caused by a programmer error." It seems that Objective-C exceptions should be used in the same places you would use an unchecked exception, and error code return values or NSError values are preferred in places where you would use a checked exception.
I think to be consistant it's nicer to use #throw with your own class that extends NSException. Then you use the same notations for try catch finally:
#try {
.....
}
#catch{
...
}
#finally{
...
}
Apple explains here how to throw and handle exceptions:
Catching Exceptions
Throwing Exceptions
Since ObjC 2.0, Objective-C exceptions are no longer a wrapper for C's setjmp() longjmp(), and are compatible with C++ exception, the #try is "free of charge", but throwing and catching exceptions is way more expensive.
Anyway, assertions (using NSAssert and NSCAssert macro family) throw NSException, and that sane to use them as Ries states.
Use NSError to communicate failures rather than exceptions.
Quick points about NSError:
NSError allows for C style error codes (integers) to clearly identify the root cause and hopefully allow the error handler to overcome the error. You can wrap error codes from C libraries like SQLite in NSError instances very easily.
NSError also has the benefit of being an object and offers a way to describe the error in more detail with its userInfo dictionary member.
But best of all, NSError CANNOT be thrown so it encourages a more proactive approach to error handling, in contrast to other languages which simply throw the hot potato further and further up the call stack at which point it can only be reported to the user and not handled in any meaningful way (not if you believe in following OOP's biggest tenet of information hiding that is).
Reference Link: Reference
This is how I learned it from "The Big Nerd Ranch Guide (4th edition)":
#throw [NSException exceptionWithName:#"Something is not right exception"
reason:#"Can't perform this operation because of this or that"
userInfo:nil];
You can use two methods for raising exception in the try catch block
#throw[NSException exceptionWithName];
or the second method
NSException e;
[e raise];
I believe you should never use Exceptions to control normal program flow. But exceptions should be thrown whenever some value doesn't match a desired value.
For example if some function accepts a value, and that value is never allowed to be nil, then it's fine to trow an exception rather then trying to do something 'smart'...
Ries
You should only throw exceptions if you find yourself in a situation that indicates a programming error, and want to stop the application from running. Therefore, the best way to throw exceptions is using the NSAssert and NSParameterAssert macros, and making sure that NS_BLOCK_ASSERTIONS is not defined.
Sample code for case: #throw([NSException exceptionWithName:...
- (void)parseError:(NSError *)error
completionBlock:(void (^)(NSString *error))completionBlock {
NSString *resultString = [NSString new];
#try {
NSData *errorData = [NSData dataWithData:error.userInfo[#"SomeKeyForData"]];
if(!errorData.bytes) {
#throw([NSException exceptionWithName:#"<Set Yours exc. name: > Test Exc" reason:#"<Describe reason: > Doesn't contain data" userInfo:nil]);
}
NSDictionary *dictFromData = [NSJSONSerialization JSONObjectWithData:errorData
options:NSJSONReadingAllowFragments
error:&error];
resultString = dictFromData[#"someKey"];
...
} #catch (NSException *exception) {
  NSLog( #"Caught Exception Name: %#", exception.name);
  NSLog( #"Caught Exception Reason: %#", exception.reason );
resultString = exception.reason;
} #finally {
completionBlock(resultString);
}
}
Using:
[self parseError:error completionBlock:^(NSString *error) {
NSLog(#"%#", error);
}];
Another more advanced use-case:
- (void)parseError:(NSError *)error completionBlock:(void (^)(NSString *error))completionBlock {
NSString *resultString = [NSString new];
NSException* customNilException = [NSException exceptionWithName:#"NilException"
reason:#"object is nil"
userInfo:nil];
NSException* customNotNumberException = [NSException exceptionWithName:#"NotNumberException"
reason:#"object is not a NSNumber"
userInfo:nil];
#try {
NSData *errorData = [NSData dataWithData:error.userInfo[#"SomeKeyForData"]];
if(!errorData.bytes) {
#throw([NSException exceptionWithName:#"<Set Yours exc. name: > Test Exc" reason:#"<Describe reason: > Doesn't contain data" userInfo:nil]);
}
NSDictionary *dictFromData = [NSJSONSerialization JSONObjectWithData:errorData
options:NSJSONReadingAllowFragments
error:&error];
NSArray * array = dictFromData[#"someArrayKey"];
for (NSInteger i=0; i < array.count; i++) {
id resultString = array[i];
if (![resultString isKindOfClass:NSNumber.class]) {
[customNotNumberException raise]; // <====== HERE is just the same as: #throw customNotNumberException;
break;
} else if (!resultString){
#throw customNilException; // <======
break;
}
}
} #catch (SomeCustomException * sce) {
// most specific type
// handle exception ce
//...
} #catch (CustomException * ce) {
// most specific type
// handle exception ce
//...
} #catch (NSException *exception) {
// less specific type
// do whatever recovery is necessary at his level
//...
// rethrow the exception so it's handled at a higher level
#throw (SomeCustomException * customException);
} #finally {
// perform tasks necessary whether exception occurred or not
}
}
There is no reason not to use exceptions normally in objective C even to signify business rule exceptions. Apple can say use NSError who cares. Obj C has been around a long time and at one time ALL C++ documentation said the same thing. The reason it doesnt matter how expensive throwing and catching an exception is, is the lifetime of an exception is exceedingly short and...its an EXCEPTION to the normal flow. I have never heard anyone say ever in my life, man that exception took a long time to be thrown and caught.
Also, there are people that think that objective C itself is too expensive and code in C or C++ instead. So saying always use NSError is ill-informed and paranoid.
But the question of this thread hasnt yet been answered whats the BEST way to throw an exception. The ways to return NSError are obvious.
So is it: [NSException raise:... #throw [[NSException alloc] initWithName....
or #throw [[MyCustomException... ?
I use the checked/unchecked rule here slightly differently than above.
The real difference between the (using the java metaphor here) checked/unchecked is important --> whether you can recover from the exception. And by recover I mean not just NOT crash.
So I use custom exception classes with #throw for recoverable exceptions, because
its likely I will have some app method looking for certain types of failures in multiple
#catch blocks. For example if my app is an ATM machine, I would have a #catch block for the
"WithdrawalRequestExceedsBalanceException".
I use NSException:raise for runtime exceptions since I have no way to recover from the exception,
except to catch it at a higher level and log it. And theres no point in creating a custom class for that.
Anyway thats what I do, but if there's a better, similarly expressive way I would like to know as well. In my own code, since I stopped coding C a hella long time ago I never return an NSError even if I am passed one by an API.