When there is no data (as can happen even when there is an Internet connection due to server issues), the following crashes. Is there a way using the error term to make it degrade gracefully or do I have to use another if statement such as is there data in the feed before calling JSONSerialization. Would like to find an alternative to another if statement. In actually, the if statement below is earlier in the code and all the ifs get very complicated.
if (internet) {
//some code
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:jsonFeed
options:kNilOptions
error:&error];
//some code
}
The only other option would be to have a method/function that wraps the call to JSONObjectWithData:options:error: and returns nil if jsonFeed is nil. You'd just be hiding the if statement, but if you need this in many places, it may be worth it.
NSJSONSerialization will handle any valid NSData object with any contents whatsoever. Passing a nil NSData is a programming error which rightfully throws an exception.
You may for example pass
jsonfeed ?: [NSData data]
instead of jsonfeed. But then why are you testing "if (internet)"? That's nonsense. What counts is not whether you have an internet connection or not, what counts is whether you had a connection to the server that returned a result.
Related
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.
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.
I have a UITableView with cells to display images and text from a largish (5000 items) JSON file. I want to stream the JSON in and start updating the UITableView immediately, but can't seem to work out the plumbing for this.
- (NSArray *)parseJSONIntoImageObjectsFromData:(NSData *)rawJSONData {
NSError *error;
NSMutableArray *arrayOfImageObjects = [[NSMutableArray alloc] init];
NSURL *myURL = [[NSURL alloc] initWithString:self.urlString];
NSData *objects = [NSData dataWithContentsOfURL:myURL];
NSInputStream *stream = [[NSInputStream alloc] initWithData:objects];
[stream open];
NSMutableArray *arrayFromStream = [NSJSONSerialization JSONObjectWithStream:stream options:NSJSONReadingAllowFragments error:&error];
for (NSDictionary *JSonDictionary in arrayFromStream) {
NSLog(#"Count is %lu", (unsigned long)arrayOfImageObjects.count);
NSInteger imgID = (NSInteger)JSonDictionary[#"id"];
ImageObject *newImageObject = [[ImageObject alloc] initWithID:imgID andTitle:JSonDictionary[#"title"] andThumbnailURL:JSonDictionary[#"thumbnailUrl"]];
[arrayOfImageObjects addObject:newImageObject];
}
return arrayOfImageObjects;
}
This definitely gets them as a stream, as the NSLog reveals in the debug window. But since it waits for the return it has to complete. I'm a little puzzled at going about this and can't find a good code sample. Do I perhaps return a stream?
EDIT: I am not terribly concerned about the brief delay I am encountering and I am sure the delay is more on the retrieval than in the parsing, I just want to learn to retrieve the data as a stream and update the UITableView incrementally as a way to do this better. I enjoy working on data retrieval and manipulation and am trying to improve my skills by knowing more.
Also, the images are retrieved asynchronously at display time using an NSOperationQueue and don't really matter for this task.
If you benchmark this, I think you'll find that the parsing time of the JSON is inconsequential. The slow parts are going to be the download of the original JSON (and possibly the creation of the ImageObject objects). You should benchmark this in Instruments (use the "time profiler" tool) and use the "record waiting threads" option. See WWDC video Building Concurrent User Interfaces on iOS for a demonstration on how to use Instruments to diagnose these sorts of issues.
I would first retire the dataWithContentsOfURL, as that runs synchronously. I would advise using an asynchronous technique such as NSURLSession method dataWithURL (or if you need support for pre-iOS 7, NSURLConnection method sendAsynchronousRequest).
Usually in these cases, the JSON is small enough, that the biggest delay stems from the network latency in making the initial request. I mention that so that you don't bother embarking on some major refactoring of the code for paging/streaming approaches without confirming that this will solve the problem.
Also, you haven't shared this ImageObject logic, but if that is synchronously loading images, that's a likely candidate for refactoring for asynchronous retrieval, too. Without knowing more about that class, it's hard to advise you further on that point.
Define NSMutableArray *arrayOfImageObjectsas a property or variable outside this method and then in your for loop, call [self.tableView reloadData] after maybe every 100 objects.
That's assuming that your numberOfRowsInSection is keying off of arrayOfImageObjects as well and cellForRowAtIndexPath is using it to populate the table data.
But also consider 'paging' your data, so as to only load 50 objects or so, at once (assuming your API supports this like 'http://example.com/imagedata?page=1'). Then if the user flicks or scrolls the tableview you can do another api call, increasing the page number and adding that new set of data to your current set and calling reloadData.
EDIT: also I'm assuming your "parseJSONIntoImageObjectsFromData" is running asynchronously. If not then use something like AFNetworking (or sendAsynchronousRequest:queue:completionHandler: in NSURLConnection) and in the completion block you can start adding to your array.
Let's say a method returns a CFErrorRef via a pointer. This returned error may be NULL. So would it be safe to perform a __bridge_transfer still or should I check for NULL.
E.g.
CFErrorRef cfError;
ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, &cfError);
NSError *error = (__bridge_transfer NSError *)cfError;
I don't see any mention of this in the documentation and CFRelease documentation specifically states This value must not be NULL.
https://developer.apple.com/library/mac/documentation/CoreFoundation/Reference/CFTypeRef/Reference/reference.html#//apple_ref/c/func/CFRelease
You do not need to check for NULL.
ARC is a strictly compile-time mechanism. When you use __bridge_transfer you are merely transferring memory management responsibility of a variable to the compiler. Whether cfError happens to be NULL or not at runtime is completely irrelevant to the compiler.
In your case, ARC will insert a release for error, but if error happens to be nil it's a simple no-op.
The error will be non NULL if the return value of the function is NULL.
The pattern for this kind of CF function is to wrap the error checking in an if statement.
if (addressBookRef == NULL) { /* your error handling here */}
You should not try to bridge anything unless it is non NULL. Object ownership or more accurately retain count and responsibility for decrementing it, are not meaningful with NULL or nil. It would be an anti pattern.
At best it's a null operation.
Sending messages to nil with Objective-C is fine, including retain and release.
It is not fine to pass a NULL value to CFRelease() or CGRetain()
The direct answer to the question is yes, you can use __bridge_transfer on NULL. But this isn't the right question.
Read the documentation on ABAddressBookCreateWithOptions. In particular, check out the documentation for error:
On error, contains error information. See “Address Book Errors.”
This is important.
error's value in the case of success is not documented.
error being nil/NULL/0 (ever) is not documented.
This isn't academic. Some APIs have historically set error to invalid values. Imagine the call set the CFError to -1. That's "valid" since the non-NULL reply means you're not supposed to interpret the error, but bridge casting -1 to a NSError will probably crash.
That means you must not touch cfError unless an error is indicated by ABAddressBookCreateWithOptions returning NULL.
CFErrorRef cfError;
NSError *error;
ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, &cfError);
if (addressBookRef == NULL) {
error = (__bridge_transfer NSError *)cfError;
}
You didn't ask this, but one additional wrinkle here is that bridges aren't even required if the compiler recognizes that something is 0-equivalent. For instance, this code will compile silently (assuming _thing1 and _thing2 are instance variables):
- (id)bar {
if (_thing1) return NO;
if (_thing2) return 0;
return NULL;
}
This is sloppy code, and I you should not do this intentionally, but knowing it builds cleanly… it's a good thing to look for. I ran into a bug caused by something like this:
- (NSNumber *)someCalculationWithError:(NSError *)error {
return 0; // meant to return #(0)
}
Unlike NSObjects, sending messages to NULL CF objects is not ok. I don't know about bridging casts specifically, but I would guess that no, casting a CF object to an NSObject using __bridge_transfer is NOT ok.
Why not try it and see? Cast it to a variable in the local scope of an instance method. That way, as soon as the method goes out of scope the system should try to release the object.
Hi I am using this library and I found the function:
- (void) queueRequest:(NSString*)urlPath completion:(void(^)(NSData*))completionWithDownloadedData;
I try to pass a simple NSData *data; and it throw an error, what really mean (void(^)(NSData*))? Is the first time that I see it.
Thanks a lot.
(void(^)(NSData*)) declares a code block.
You can call your function this way.
[obj queueRequest:urlPath completion:^(NSData* data){
/* some code */
}];
data is a parameter to your block, which you can work with. The block will be called when the queueRequest will finish, asynchronously.
The interface is asynchronous, meaning that the data will only be available sometime later. This means that the method can’t simply return the NSData* (without blocking for all the time, which is impractical). The problem is nowadays often solved with blocks, and the completion argument here is a block that takes an NSData* argument and returns void. This is how you call such a method:
[foo queueRequest:path completion:^(NSData *receivedData) {
NSLog(#"Received data: %#", receivedData);
}];
The call will return immediately and the block will be executed sometime later, when the data is available.
It's a block that accepts a NSData object as it's only argument and returns nothing.
See Apple's Blocks Programming Topics.