Losing responseData in NSURLConnection wrapper - objective-c

I'm having some trouble implementing a NSURLConnection wrapper. I'm creating an NSURLConnection with POST data, and the connection appears to receive a response as well as data. The didReceiveData callback logs the responseData length, which is 2000 something bytes. However, by the time didFinishLoading fires, responseData contains 0 bytes. Any tips on where to look for what could be modifying the contents of responseData? It gets reset in didReceiveResponse, but didReceiveResponse does not appear to be called in between didReceiveData and didFinishLoading.
Here is some output from the log:
Current language: auto; currently objective-c 2012-01-24 13:35:40.020
PSIdea[24007:11903] didReceiveResponse: responseData length:(0)
warning: Attempting to create USE_BLOCK_IN_FRAME variable with block
that isn't in the frame. 2012-01-24 13:35:40.604 PSIdea[24007:11903]
didReceiveData. responseData length:(2233) 2012-01-24 13:35:40.604
PSIdea[24007:11903] didFinishLoading: responseData length:(0)
2012-01-24 13:35:41.881 PSIdea[24007:11903] responseData as string:
2012-01-24 13:35:41.882 PSIdea[24007:11903] responseData as
dictionary:
Here is the relevant code:
NetworkController.m
-(void)postRequestToURL:(NSURL*)url withData:(NSData*)data withContentType:(NSString*)contentType
{
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"POST"];
if (!contentType)
{
contentType = #"application/x-www-form-urlencoded";
}
[request setValue:contentType forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:data];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
-(void)connection:(NSConnection*)conn didReceiveData:(NSData*)data
{
[_responseData appendData:data];
NSLog(#"didReceiveData. responseData length:(%d)", _responseData.length);
}
-(void)connection:(NSConnection*)conn didReceiveResponse:(NSURLResponse *)response
{
if (_responseData == NULL) {
_responseData = [[NSMutableData alloc] init];
}
[_responseData setLength:0];
NSLog(#"didReceiveResponse: responseData length:(%d)", _responseData.length);
}
-(void)connection:(NSConnection*)conn didFailWithError:(NSError *)error
{
NSLog([NSString stringWithFormat:#"Connection failed: %#", error.description]);
}
PSINetworkController.m (subclass)
-(void)connectionDidFinishLoading:(NSURLConnection*)connection
{
NSLog(#"didFinishLoading: responseData length:(%d)", _responseData.length);
NSString *responseString = [[NSString alloc] initWithData:_responseData encoding:NSUTF8StringEncoding];
NSLog(#"responseData as string: %#", responseString);
SBJsonParser* parser = [[SBJsonParser alloc] init];
NSDictionary* dict = [parser objectWithData:_responseData];
NSLog(#"responseData as dictionary:");
for (id key in dict) {
NSLog(#"%#=%#", key, [dict objectForKey:key]);
}
[_delegate connection:connection receivedResponse:dict];
}
Thanks.
EDIT:
Also, I seem to have stumbled across a solution. The problem does have something to do with the way _responseData is declared.
Declaring response data as an atomic property leads to the response data being reset just as before.
#property (retain) NSMutableData* responseData;
However, simply declaring the variable in the interface seems to eliminate the problem - the data persists from didReceiveData to didFinishLoading.
#interface NetworkController : NSObject
{
NSMutableData* _responseData;
}
It is my understanding that declaring properties simply generates setters and getters, but I'm failing to see how this relates in this situation. Can anyone explain?
EDIT: I neglected to mention that this project is using ARC.

if you have declared responseData as retain if your .h file, you should change the code to:
-(void)connection:(NSConnection*)conn didReceiveResponse:(NSURLResponse *)response
{
if (self._responseData) {
self._responseData = [[NSMutableData alloc] init] autorelease];
}
//further code
}
this should resolve your issue

Related

How to know the response at - (void)connectionDidFinishLoading:(NSURLConnection *)connection?

- (void)connectionDidFinishLoading:(NSURLConnection *)connection has only one parameter.
connection.
However, I do not see any member of connection that can tell what is the data being returned by the resources.
I checked
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSURLConnection_Class/Reference/Reference.html#//apple_ref/doc/c_ref/NSURLConnection
If we do
sendSynchronousRequest:returningResponse:error: we get something in return.
NSData * response= [NSURLConnection sendSynchronousRequest:URLRequest returningResponse:&URLResponse error:&error];
NSString * result = [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding];
here, result is what's actually returned.
But what about if we send this request asynchronously?
You need to implement more then just that one callback (for async calls).
Build a buffer in didReceiveData, then on the finished event you can use the buffer.
Like so:
// in .h NSMutableData *_data;
-(void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response
{
_data = [[NSMutableData alloc] init]; // _data being an ivar
}
-(void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
{
[_data appendData:data];
}
-(void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error
{
// handle connection error
}
-(void)connectionDidFinishLoading:(NSURLConnection*)connection
{
// do something with _data
}

NSURLConnection is not calling didFailWithError.

I am attempting to write a bit of code that checks the URL of a datasource, then populates an array with objects from that URL. It actually works well, but if there is a problem with the web connection or the address I want to populate the array with data from a bundled file. The issue I am having is that the connection didFailWithError method is never called. I tried passing a simple string but it does not call. I want the app to still function for people who are using ipod touch or are in airplane mode.
connection didReceiveResponse is working without issue.
This is what I'm working with.
- (void)loadListData{
NSLog(#"Loading data from sources");
NSURLRequest *listURLRequest = [NSURLRequest requestWithURL:integerPhoneListURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:1.0];
[[NSURLConnection alloc] initWithRequest:listURLRequest delegate:self];
if (!listConnectFail){
phoneListJSON =[NSData dataWithContentsOfURL:integerPhoneListURL];
[self performSelectorOnMainThread:#selector(fetchedData:) withObject:phoneListJSON waitUntilDone:YES];
} else {
//This will tell us if there is an error loading the file
NSLog(#"File not found on web init from file");
phoneListJSON =[NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"contactlist" ofType:#"json"]];
[self performSelectorOnMainThread:#selector(fetchedData:) withObject:phoneListJSON waitUntilDone:YES];
}
//Initialize the filtered list with array of customer objects. Based on original data
filteredList = [[NSMutableArray alloc] init];
for (NSDictionary *dict in phoneListOriginal) {
contact *single = [[contact alloc] init];
single.fName = [dict objectForKey:#"fName"];
single.lName = [dict objectForKey:#"lName"];
single.extension = [dict objectForKey:#"extension"];
single.title = [dict objectForKey:#"title"];
single.department = [dict objectForKey:#"department"];
single.cellNumber = [dict objectForKey:#"cellNumber"];
//NSLog(#"%#", single.lName);
[filteredList addObject:single];
}
NSLog(#"Array filteredLIst contains %d records",[filteredList count]); }
-(void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
listConnectFail = YES;
NSLog(#"Connection Failed, pulling from file"); }
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
listConnectFail = NO;
NSLog(#"Connection Succeeded, populating from API");
}
I know it is probably something stupid that I am not seeing, but I could use the help to see what I don't
Thanks in advance!
How did you confirm that your delegate did not receive the message? Did you check the log?
Your code seems to assume that 'listConnectFail' will be set immediately after the NSURLConnection's init is done, which is not necessarily the case.
[[NSURLConnection alloc] initWithRequest:listURLRequest delegate:self];
if (!listConnectFail){...}
The NSURLConnection documentation states that 'The delegate will receive delegate messages as the load progresses.'
However, I am not sure about the airplane mode, maybe this particular error can be detected synchronously.

json-framework - Token 'start of array' not expected after outer-most array or object

Feeding the json-parser with the this data: http://mapadosradares.com.br/api/get_initial_load yields this error: Token 'start of array' not expected after outer-most array or object
Here is my code:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(#"Connection didReceiveData of length: %u", data.length);
// Printing the received data
size_t length = [data length];
unsigned char aBuffer[length];
[data getBytes:aBuffer length:length];
//aBuffer[length - 1] = 0;
NSLog(#"\n\n\n\n%s\n\n\n\n", aBuffer);
SBJsonStreamParserStatus status = [parser parse:data];
if (status == SBJsonStreamParserError) {
NSLog(#"Parser error: %#", parser.error);
} else if (status == SBJsonStreamParserWaitingForData) {
NSLog(#"Parser waiting for more data");
}
}
As far as I can tell the JSON is perfectly fine. Any thoughts?
UPDATE:
Here's the parser initalization:
- (void) getInitialLoad
{
adapter = [[SBJsonStreamParserAdapter alloc] init];
parser = [[SBJsonStreamParser alloc] init];
adapter.delegate = self;
parser.delegate = adapter;
NSString *url = #"http://mapadosradares.com.br/api/get_initial_load";
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]
cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
Are you properly initializing the parser between requests? You haven't shown your code, but it seems like this would be a reasonable error to expect if you ran two successive calls to the feed through the parser.
By the way, I ran the feed output through the excellent JSON parser at http://jsonlint.com and it does appear to be fine.

JSON broken in Objective-C URL Request

After pounding my head all day long, I am down to StackOverflow to pull me through.
I am making a NSURLRequest in my iPhone App ...
NSURL* url = [[self serviceUrl] URLByAppendingPathComponent:[NSString stringWithFormat:#"Json"]];
NSString* json = [NSString stringWithFormat:#"{\"id\":\"%#\"}", id];
NSMutableURLRequest* urlRequest = [NSMutableURLRequest requestWithURL:url];
[urlRequest setHTTPMethod:#"POST"];
[urlRequest setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[urlRequest setHTTPBody:[json dataUsingEncoding:NSUTF8StringEncoding]];
this is returning to me a JSON string, which comes back to me but it is broken in the since that the string will not parse correctly.
However if I make a normal request to the same url in the Safari browser then JSON is returned correctly. I am validating this JSON here.
So whats the deal? Is there a limit to the length of data in a NSString* that a 32Kb json file would not be stored in memory correctly? Sometimes the JSON can be parsed, which leads me to believe that I am not clearing my JSON string correctly after each request.
_json = [[NSString alloc] initWithData:_dataResponse encoding:NSUTF8StringEncoding];
NSArray* retrievedData = (NSArray*)[_json JSONValue];
// removed for brevity
_json = #"";
Other information, I am using ASP.NET MVC 3 to provide the web services for this app.
EDIT
- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse
{
_loader.hidden = NO;
[_loadingIndicator startAnimating];
return request;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
_dataResponse = [[NSMutableData alloc] init];
[_dataResponse setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[_dataResponse appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(#"Error receiving response: %#", error);
_loader.hidden = YES;
[_loadingIndicator stopAnimating];
}
- (void)connectionDidFinishLoading:(NSURLConnection*)connection
{
_json = [[NSString alloc] initWithData:_dataResponse encoding:NSUTF8StringEncoding];
// Removed for brevity
_json = #"";
_loader.hidden = YES;
[_dataResponse release];
_dataResponse = nil;
[_loadingIndicator stopAnimating];
}
FINAL SOLUTION
I was making multiple calls to have data already stored for views in order to switch views in a tab bar controller. I wasn't checking the connection during the appending of the data, I was checking the connection when it was finished in order to store the data correctly. My final solution was to make each call sychronously after the previous one during the finished method call.
Unfortunately, nothing blatant is jumping out at me.
Some things to try that will hopefully help:
double check your NSUrlConnection pattern against this doc. The only diff I see is they're doing [[NSMutableData data] retain] instead of alloc, init. They also create NSMutableData with the connection request (not in response) and only length=0 in response. Not sure why it would matter though ...
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/URLLoadingSystem/Tasks/UsingNSURLConnection.html
try something like Charles Proxy. It allows you to sniff the wire. You can see what's different between browser and simulator/phone access. At a minimum, you'll see what's coming over the wire in the bad cases.
add lots of logging. everytime you append data, log some details. After you convert to string and before the json call, log. Log sizes, etc... Something may offer you a hint.
the use of _json string seems a bit off. You're reallocating and then setting to empty string. You should either have an iVar that you alloc and release (not set to "") or create a #property with retain, copy.
You should definitely include charset=utf8 in you content-type header. And how do you know that the response you get is encoded as utf8? You should also set content-size header.

How to parse nested JSON objects with JSON framework and Objective-C/iPhone/Xcode?

I'm writing an iPhone native app using the JSON framework.
My app is accessing web services using JSON. The JSON data we send has nested objects, below is an example of the data served up:
{
"model": {
"JSONRESPONSE": {
"authenticationFlag": true,
"sessionId": "3C4AA754D77BFBE33E0D66EBE306B8CA",
"statusMessage": "Successful Login.",
"locId": 1,
"userName": "Joe Schmoe"
}
}
}
I'm having problem parsing using the objectForKey and valueForKey NSDictionary methods. I keep getting invalidArgumentException runtime errors.
For instance, I want to query the response data for the "authenticationFlag" element.
Thanks,
Mike
Seattle
It is hard to tell without some more details (e.g. the JSON parsing code that you are using), but two things strike me as possible:
you are not querying with a full path. In the case above, you'd need to first get the enclosing model, the json response, and only then ask the json response dictionary for the authenticationFlag value:
[[[jsonDict objectForKey:#"model"]
objectForKey:#"JSONRESPONSE"] objectForKey:#"authenticationFlag"]
perhaps you're using c-strings ("") rather than NSStrings (#"") as keys (although this would likely crash nastily or just not compile). The key should be something than can be cast to id.
While possible, both are probably false, so please include more detail.
The following is taken directly from Dan Grigsby's Tutorial at - http://mobileorchard.com/tutorial-json-over-http-on-the-iphone/ - Please attribute, stealing is bad karma.
Fetching JSON Over HTTP
We’ll use Cocoa’s NSURLConnection to issue an HTTP request and retrieve the JSON data.
Cocoa provides both synchronous and asynchronous options for making HTTP requests. Synchronous requests run from the application’s main runloop cause the app to halt while it waits for a response. Asynchronous requests use callbacks to avoid blocking and are straightforward to use. We’ll use asynchronous requests.
First thing we need to do is update our view controller’s interface to include an NSMutableData to hold the response data. We declare this in the interface (and not inside a method) because the response comes back serially in pieces that we stitch together rather than in a complete unit.
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController {
IBOutlet UILabel *label;
NSMutableData *responseData;
}
To keep things simple, we’ll kick off the HTTP request from viewDidLoad.
Replace the contents of :
#import "JSON/JSON.h"
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
responseData = [[NSMutableData data] retain];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"XYZ.json"]];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[responseData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[responseData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
label.text = [NSString stringWithFormat:#"Connection failed: %#", [error description]];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[connection release];
}
- (void)dealloc {
[super dealloc];
}
#end
This mostly boilerplate code initializes the responseData variable to be ready to hold the data and kicks off the connection in viewDidload; it gathers the pieces as they come in in didReceiveData; and the empty connectionDidFinishLoading stands ready to do something with the results.
Using The JSON Data
Next, we’ll flesh out the connectionDidFinishLoading method to make use of the JSON data retrieved in the last step.
Update the connectionDidFinishLoading method :
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[connection release];
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
[responseData release];
NSArray *luckyNumbers = [responseString JSONValue];
NSMutableString *text = [NSMutableString stringWithString:#"Lucky numbers:\n"];
for (int i = 0; i < [luckyNumbers count]; i++)
[text appendFormat:#"%#\n", [luckyNumbers objectAtIndex:i]];
label.text = text;
}
It creates an NSArray. The parser is very flexible and returns objects — including nested objects — that appropriately match JSON datatypes to Objective-C datatypes.
Better Error Handling
Thus far, we’ve been using the the convenient, high-level extensions to NSString method of parsing JSON. We’ve done so with good reason: it’s handy to simple send the JSONValue message to a string to accessed to the parsed JSON values.
Unfortunately, using this method makes helpful error handling difficult. If the JSON parser fails for any reason it simply returns a nil value. However, if you watch your console log when this happens, you’ll see messages describing precisely what caused the parser to fail.
It’d be nice to be able to pass those error details along to the user. To do so, we’ll switch to the second, object-oriented method, that the JSON SDK supports.
Update the connectionDidFinishLoading method in :
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[connection release];
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
[responseData release];
NSError *error;
SBJSON *json = [[SBJSON new] autorelease];
NSArray *luckyNumbers = [json objectWithString:responseString error:&error];
[responseString release];
if (luckyNumbers == nil)
label.text = [NSString stringWithFormat:#"JSON parsing failed: %#", [error localizedDescription]];
else {
NSMutableString *text = [NSMutableString stringWithString:#"Lucky numbers:\n"];
for (int i = 0; i < [luckyNumbers count]; i++)
[text appendFormat:#"%#\n", [viewcontroller objectAtIndex:i]];
label.text = text;
}
}
Using this method gives us a pointer to the error object of the underlying JSON parser that we can use for more useful error handling.
Conclusion :
The JSON SDK and Cocoa's built-in support for HTTP make adding JSON web services to iPhone apps straightforward.
NSString* aStr;
aStr = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSDictionary *dictionary = [aStr JSONValue];
NSArray *keys = [dictionary allKeys];
// values in foreach loop
for (NSString *key in keys) {
NSArray *items = (NSArray *) [dictionary objectForKey:key];
for (id *item in items) {
NSString* aStrs= item;
NSLog(#" test %#", aStrs);
NSDictionary *dict = aStrs;
NSArray *k = [dict allKeys];
for (id *it in k) {
NSLog(#"the child item: %#", [NSString stringWithFormat:#"Child Item -> %# value %#", (NSDictionary *) it,[dict objectForKey:it]]);
}
Now, Objective c has introduced in build class for JSON Parsing.
NSError *myError = nil;
NSDictionary *resultDictionary = [NSJSONSerialization JSONObjectWithData:self.responseData options:NSJSONReadingMutableLeaves error:&myError];
http://developer.apple.com/library/ios/#documentation/Foundation/Reference/NSJSONSerialization_Class/Reference/Reference.html
So utilize this class and make your application error free...:)