-(void) conn:(NSString *)method{
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
__block NSDictionary *resultBlock = nil;
dispatch_sync(concurrentQueue, ^{
/* Download the json here */
//Create webservice address
NSString *webService = [_baseURL stringByAppendingString:_webService];
//NSLog(#"%#", webService);
//Create error object
NSError *downloadError = nil;
//Create the request
NSMutableURLRequest *req = [self initRequest:webService method:method];
if(req != nil){
//Request the json data from the server
NSData *jsonData = [NSURLConnection
sendSynchronousRequest:req
returningResponse:nil
error:&downloadError];
if(downloadError!=nil){
NSLog(#"DOWNLOAD ERROR %#", downloadError);
}
NSError *error = nil;
id jsonObject = nil;
if(jsonData !=nil){
/* Now try to deserialize the JSON object into a dictionary */
jsonObject = [NSJSONSerialization
JSONObjectWithData:jsonData
options:kNilOptions
error: &error];
}
//Handel the deserialized object data
if (jsonObject != nil && error == nil){
NSLog(#"Successfully deserialized...");
if ([jsonObject isKindOfClass:[NSDictionary class]]){
resultBlock = (NSDictionary *)jsonObject;
//NSLog(#"Deserialized JSON Dictionary = %#", resultBlock);
}
else if ([jsonObject isKindOfClass:[NSArray class]]){
NSArray *deserializedArray = (NSArray *)jsonObject;
NSLog(#"Deserialized JSON Array = %#", deserializedArray);
} else {
/* Some other object was returned. We don't know how to deal
with this situation, as the deserializer returns only dictionaries
or arrays */
}
}
else if (error != nil){
NSLog(#"An error happened while deserializing the JSON data. %#", error);
}else{
NSLog(#"No data could get downloaded from the URL.");
//[self conn:method];
}
}
});
dispatch_sync(dispatch_get_main_queue(), ^{
/* Check if the resultBlock is not nil*/
if(resultBlock != nil){
/*Set the value of result. This will notify the observer*/
[self setResult:resultBlock];
}
});
});
}
Why do I get the following error?
An error happened while deserializing the JSON data. Error
Domain=NSCocoaErrorDomain Code=3840 "The operation couldn’t be
completed. (Cocoa error 3840.)" (JSON text did not start with array or
object and option to allow fragments not set.) UserInfo=0x20839f80
{NSDebugDescription=JSON text did not start with array or object and
option to allow fragments not set.}
When I change it to
/* Now try to deserialize the JSON object into a dictionary */
jsonObject = [NSJSONSerialization
JSONObjectWithData:jsonData
options:NSJSONReadingAllowFragments
error: &error];
}
I get the following error:
An error happened while deserializing the JSON data. Error
Domain=NSCocoaErrorDomain Code=3840 "The operation couldn’t be
completed. (Cocoa error 3840.)" (Invalid value around character 0.)
UserInfo=0x20888760 {NSDebugDescription=Invalid value around character
0.}
I changed my connection from LTE to wifi and now I get
504 error and NSLog(#"No data could get downloaded from the URL.");
You should fix these issues in your code first:
Properly check for errors in methods which provide a pointer to a reference to an NSError object as the last parameter, e.g.: - (BOOL) doSomething:(NSError**)error, or -(NSData*) doSomething:(NSError**)error
In order test for an error correctly, you have to check the return value of the method only. Those methods indicate an error condition with a "special return value". For example, they return NO or nil - as always specified in the documentation. Only after the method indicated an error, the provided error parameter contains a meaningful value - that is, it points to an NSError object created by the method. Note that this parameter may also become none NULL when the method succeeded, in which case that has no "meaning".
Web services usually can provide several formats of the requested resource. If you don't specify which format you want the server to encode the resource, you get a default format - which is not necessarily JSON.
In order to be explicit about the desired format of the resource, set a corresponding "Accept" header. For example, if you wish the format in JSON you would set a header: "Accept: application/json" in your request.
Web services may have reasons not to respond with the resource you requested. In order to be sure you got the response that you requested, you need to check the response for status code and MIME type in order to ensure you actually received a JSON response.
It seems, you are a bit uncertain about how to use dispatch functions to your advantage. If you use the synchronous convenient method sendSynchronousRequest:... You certainly need to wrap it in only one dispatch_async function. If you then want to set the result on the main thread, you certainly want to use dispatch_async, not dispatch_sync.
However, it would be an improvement if you would use sendAsynchronousRequest:... instead. And only if you would use NSURLConnection in asynchronous mode and implement the NSURLConnection delegate methods - which I strongly recommend - it would actually become great ;)
So, I think, once you fixed your code, you may be able to answer the original question yourself, or get better error responses from the server, or the error magically disappeared ;)
Related
What is the correct way to handle NSError** pointers?
- (BOOL)handleData:(NSDictionary *)data error:(NSError **)error {
// pass the error pointer to NSJSONSerialization
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:data options:options error:error];
// Check if NSJSONSerialization had errors
if (error) // <-- sometimes this works, sometimes it crashes...
return false;
...
return true;
}
- (void)someMethod {
NSError *error = nil;
BOOL result = [self handleData:dataDict error:&error];
if (error) {
// an error occurred
} else {
}
}
In this example someMethod passes a NSError reference to handleData:error. This is done by passing a pointer/address instead of the object (...error:&error)
The method handleData:error then passes this pointer to dataWithJSONObject:options:error (now without the &). Now I would like to check an error occurred, but what is the correct way to do this?
if (error)...
// This works if error == nil. However this is not always the case.
// Sometimes error is some address (e.g. 0x600001711f70) and *error == nil
// from the start of the method (passing error to NSJSONSerialization has no
// influence on this
if (*error)...
// This works in cases where error itself is not nil, but it crashes if
// error == nil
Why is error == nil in some cases and error != nil but *error == nil in others?
What is the correct way to pass error between the methods and to check if an error occurred?
The place to find the answers is Introduction to Error Handling Programming Guide For Cocoa. The conventions are:
A method may return an NSError object via an NSError ** parameter, such a method should also have a non-void return type and indicate success or failure via its return value. So, using your example, dataWithJSONObject:options:error: will return nil if it encounters an error and may return an error object via its third parameter.
Any method accepting an NSError ** parameter for error returns should accept either the address of an NSError * variable or NULL. The latter value means the user does not wish to have an error object returned. This means that the method accepting an NSError ** parameter must check the parameter value is not NULL before attempting to assign an error object via it.
So your method handleData:error: must be prepared to accept NULL and needs to test for it. Your code therefore must include something similar to:
// Check if NSJSONSerialization had errors
if (jsonData == nil)
{
// Error occurred, did it return an error object?
if (error != NULL && *error != nil)
{
// we have an error object
}
else
{
// we have an error but no error object describing it
}
}
else
{
// no JSON error
}
HTH
This is the code i have so far
// Parse data using NSJSONSerialization
NSError *error = nil;
NSArray *JsonArray = [NSJSONSerialization JSONObjectWithData:myData options:NSJSONReadingMutableContainers error: &error];
if(!JsonArray)
{
NSLog(#"Error Parsing Data: %#", error);
}
else
{
for(NSDictionary *event in JsonArray)
{
if([[event description] isEqualToString:#"error"])
{
// Get error number? I am confused by this part
NSLog(#"Element: %#", [event objectForKey:#"error"]);
}
else
{
NSLog(#"Element: %#", [event description]);
}
}
}
this is the JSON Data that parses correctly:
[{data string}, {data strings}]
This only gives me the string "error" and not the int as well:
{"error":0}
I am echoing this data from a PHP script if that helps any. Am i just doing it wrong, or did i miss something?
Your problem is that when you receive an error, you get back an NSDictionary and not an NSArray. This should work:
if ([jsonObject isKindOfClass:[NSArray class]]) {
// no error: enumerate objects as you described above
} else if ([jsonObject isKindOfClass:[NSDictionary class]]) {
// error: obtain error code
NSNumber *errCode = jsonObject[#"error"];
} else {
// something bad's happening
}
Stylistic pieces of advice:
Don't call your object JsonArray, since it's not always an array. Call it jsonObject.
Don't start variable names with capital letters.
Would be great if you had posted the complete JSON document that you are trying to parse, because without that, there is absolutely no chance to figure out whether your code is anywhere near correct. The example [{data string}, {data strings}] that you gave is most definitely not a correct JSON document, so trying to parse it will return nil. {"error":0} is a dictionary with a single key "error" and a value 0. Having dictionaries with a single key is let's say unusual.
A JSON document contains either an array or object (using JSON terms) which will be turned either into an NSArray* or an NSDictionary*. You should know whether you expect an array or dictionary. If you expect an NSArray, check that [jsonObject isKindOfClass:[NSArray class]]. If you expect an NSDictionary, check that [jsonObject isKindOfClass:[NSDictionary class]]. If you don't do that then the wrong JSON document will either crash your app or produce total nonsense.
If you have an array then you will usually iterate through the elements of the array and handle each one in turn. If you have a dictionary you will usually look up keys that you know how to handle. What you are doing, iterating through an array of dictionaries, and checking for a dictionary with a key of "error", that's a very strange design of your JSON document.
And lookup what the "description" method does. "description" is what NSLog calls to find out what to print when it is asked to print an object. For an NSDictionary with a single key "error" and a value 0, it would return something like "error:0" which is of course not the same as "error".
NSDictionary *jsonDic = [NSJSONSerialization JSONObjectWithData:myData options:NSJSONReadingMutableContainers error:&error];
NSLog(#"jsonDic: %#", [jsonDic objectForKey:#"string"]);
By going to a url test.com/test.cfm I am able to output the following:
{"COLUMNS":["OBJID","USERNAME","USERPASSWORD","USERFNAME","USERMI","USERLNAME","COMPANY","TBLCOMPANYID","TBLTITLE","USERADDRESS1","USERADDRESS2","USERCITY","USERSTATE","USERZIP","USERSIGNATUREFILE","USERBUSINESSNUMBER","USERBUSINESSEXT","USERFAXNUMBER","USERCELLNUMBER","USEROTHERNUMBER","USEREMAIL1","USEREMAIL2","USEREMAIL3","DEFAULTPROJECTID","SIGNATURE","SIGNATUREUPLOADBY","SORTORDER","DISABLESTATUS","UUID","SITEID","PROGRAMID"],
"DATA":[[1,"test",11214.0,"admin","","admin","adf Inc.",1,1,"admin","","","California","","","",null,"","","","admin#test.com","","",0,null,null,0,false,"468373c5-1234-1234-1234-3133a2bb1679",62,1]]}
To iterate through this I will first need to get the data using this?
NSMutableData *receivedData;
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:#"test.com/test.cfm"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// create the connection with the request
// and start loading the data
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:receivedData];
if (theConnection) {
// Create the NSMutableData to hold the received data.
// receivedData is an instance variable declared elsewhere.
NSLog(#"test = %#",receivedData);
} else {
// Inform the user that the connection failed.
}
Am I on the right track? The output says null...
You've not assigned anything to receivedData; it'll either be nil (under ARC) or an undefined value which may or may not be nil (under non-ARC). You've created an object that could be used to initiate a URL connection but done nothing with it. You're also probably not getting a valid NSURL because you've failed to specify the URI scheme (ie, the http://).
Probably the easiest thing to do (assuming at least iOS 5 and/or OS X v10.7) would be to use NSURLConnection's +sendAsynchronousRequest:queue:completionHandler: and then NSJSONSerialization to parse the result. E.g.
NSURLRequest *theRequest =
[NSURLRequest
requestWithURL:[NSURL URLWithString:#"http://test.com/test.cfm"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// or just use [NSURLRequest requestWithURL:[NSURL ...]], since the
// protocol cache policy and a 60s timeout are the default values anyway
[NSURLConnection
sendAsynchronousRequest:theRequest
queue:[NSOperationQueue mainQueue]
completionHandler:
^(NSHTTPURLResponse *urlResponse, NSData *data, NSError *error)
{
// catch all for connection errors...
if(
(urlResponse.statusCode < 200) ||
(urlResponse.statusCode >= 300) || // bad status code, e.g. 404
error || // error is non-nil would imply some other error
![data length] // data returned was empty
)
{
// report some sort of connection error
return;
}
NSError *jsonError = nil;
id <NSObject> returnedJSONObject =
[NSJSONSerialization
JSONObjectWithData:data options:0 error:&jsonError];
if(jsonError)
{
// server returned unintelligible JSON...
return;
}
NSLog(#"Got object %#", returnedJSONObject);
// then, e.g.
if(![jsonObject isKindOfClass:[NSDictionary class]])
{
// server didn't return a dictionary
return;
}
NSArray *columns = [jsonObject objectForKey:#"COLUMNS"];
if(![columns isKindOfClass:[NSArray class]])
{
// server returned no COLUMNS object, or that
// object wasn't an array
return;
}
NSLog(#"columns are %#", columns);
/* etc, etc */
}];
The class type checking stuff quickly gets quite tedious if you don't find a way to automate it but that's all validation stuff not directly related to your question.
What the above achieves is that it sends an asynchronous (ie, non-blocking) request for the contents of the URL. Results are accumulated on the main queue (ie, the same place you'd normally do user interactions). Once that entire HTTP operation is complete the code you specified at the bottom is called and that validates and parses the response. It does so synchronously so will block but that's not worth worrying about unless profiling shows it's worth worrying about.
The built-in parser is used for the JSON and everything down to 'Got object' is really just ensuring that the fetch and parse succeeded. It may not be complete — if you can think of anything else that might go wrong then don't assume I've ignored it on purpose.
At that point you just have an object of unknown type but it'll normally be a dictionary or an array because of the fundamentals of JSON. So the example code tests that it really is a dictionary then uses the normal NSDictionary interface to obtain an object for the 'COLUMNS' key. If you attempted to call objectForKey: on an array you'd raise an exception since arrays don't implement that method.
It's then fairly rote — the code checks that the object stored as 'COLUMNS' was an array. Per the JSON rules it could have been another dictionary or a string or one of a few other things. Possibly of interest is that the code calls isKindOfClass: to test that an object was found and that it was an array in a single call; that works because it's explicitly permissible to send any message to nil and the result will always be nil, which looks the same as a BOOL NO.
Xcode 4.3
I've read the SO questions on NSError**, so I wrote a simple test program that uses a slightly different syntax recommended by Xcode 4.3 (see __autoreleasing below), so I'm not 100% sure if this is correct, although the code does appear to function properly. Anyway, just a simple file reader, prints an error if the file can't be found.
Questions
Would like to know if the NSError initialization, argument passing using &, and error condition checking are correct.
Also, in the readFileAndSplit.. method, I noticed a big difference between if(!*error) and if(!error), in fact, if(!error) does not work when no error condition is raised.
File Reading Method w/Possible Error Condition
-(NSArray*) readFileAndSplitLinesIntoArray:(NSError *__autoreleasing *) error {
NSString* rawFileContents =
[NSString stringWithContentsOfFile:#"props.txt"
encoding:NSUTF8StringEncoding
error:error
NSArray* fileContentsAsArray = nil;
if(!*error)
fileContentsAsArray =
[rawFileContents componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
return fileContentsAsArray;
Caller
SimpleFileReader* reader = ...
NSError* fileError = nil;
NSArray* array = [reader readFileAndSplitLinesIntoArray: &fileError];
if(fileError){
NSLog(#"Error was : %#, with code: %li",
[fileError localizedDescription],(long)[fileError code]);
}
There are a couple of issues.
First, As per Apple's Error Handling Programming Guide, you should be checking a method's return value to determine whether a method failed or not, and not NSError. You only use NSError to get additional error information in the event that the method failed.
E.g.:
NSArray* fileContentsAsArray = nil;
NSString* rawFileContents = [NSString stringWithContentsOfFile:#"props.txt"
encoding:NSUTF8StringEncoding
error:error];
if (rawFileContents)
{
// Method succeeded
fileContentsAsArray = [rawFileContents ...];
}
return fileContentsAsArray; // may be nil
Second, NSError out parameters are typically optional and may be NULL. But if you pass a NULL error variable into your method it will crash on this line:
if (!*error) {
because you're dereferencing a NULL pointer. Instead, you must always check for NULL before referencing a pointer, like so:
if (error && *error)
{
// Do something with the error info
}
However, if you rewrite the method as indicated above then you won't be accessing the error variable at all.
I got into the habit of coding my error handling this way:
NSError* error = nil;
NSDictionary *attribs = [[NSFileManager defaultManager] removeItemAtPath:fullPath error:&error];
if (error != nil) {
DLogErr(#"Unable to remove file: error %#, %#", error, [error userInfo]);
return;
}
But looking at the documentation It seems like I got this wrong.:
- (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error
If an error occurs, upon return contains an NSError object that describes the problem. Pass NULL if you do not want error information.
Technically there is no difference between nil and NULL so does this mean I'm actually turning this off and will never get a error message (even if the delete in the above example did fail) ?
Is there a better way to code this ?
Thanks.
First off, the following line doesn't really make sense:
NSDictionary *attribs = [[NSFileManager defaultManager]
removeItemAtPath:fullPath error:&error];
-removeItemAtPath:error: returns a BOOL value, not a dictionary.
I think I see what you’re wondering about with the NULL value. Notice carefully though, how there are 2 *'s in the error parameter in the method signature:
- (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error
That means a pointer to a pointer. When you pass in &error, you are passing in the address of the pointer to the NSError. (Ugh, someone else can probably help me out here, as my head still starts to swim when dealing with pointers to pointers). In other words, even though you have set error to nil, you aren't passing in error to the method, you're passing in &error.
So, here’s what the re-written method should look like:
// If you want error detection:
NSError *error = nil;
if (![[NSFileManager defaultManager] removeItemAtPath:fullPath
error:&error]) {
NSLog(#"failed to remove item at path; error == %#", error);
// no need to log userInfo separately
return;
}
// If you don't:
if (![[NSFileManager defaultManager] removeItemAtPath:fullPath
error:NULL]) {
NSLog(#"failed to remove item at path");
return;
}
Passing NULL means the following:
BOOL itemRemoved = [[NSFileManager defaultManager] removeItemAtPath:fullPath
error:NULL];
i.e., the error parameter is NULL. Internally, -removeItemAtPath:error: sees if a valid pointer was passed. If it’s NULL, it simply won’t report the error as an NSError instance — but the return value will indicate whether the method completed successfully.
Also, your test is wrong. You shouldn’t be using the error output parameter to detect if an error occurred because it might be set even if the method completes successfully. Instead, you should use the return value of the method to detect errors. If the return value (in this particular case) is NO, then use the error output parameter to get information about the error:
NSError *error = nil;
BOOL itemRemoved = [[NSFileManager defaultManager] removeItemAtPath:fullPath error:&error];
if (itemRemoved == NO) {
DLogErr(#"Unable to remove file: error %#, %#", error, [error userInfo]);
return;
}
Quoting the Error Handling Programming Guide,
Important: Success or failure is indicated by the return value of the method. Although Cocoa methods that indirectly return error objects in the Cocoa error domain are guaranteed to return such objects if the method indicates failure by directly returning nil or NO, you should always check that the return value is nil or NO before attempting to do anything with the NSError object.
Edit: As NSGod pointed out, -removeItemAtPath:error: returns BOOL, not NSDictionary *. I’ve edited my answer to reflect that as well.
No I do it the same way and it works just fine for detecting errors. You are not passing NULL to it you are passing a pointer to NULL to it which is a very different thing. Although another option you might want to add is.
if (error != nil){...
}else{
[NSApp presentError:error]
}