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

- (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
}

Related

Data transfer between NSOperations

I would like to obtain the following: I have two NSOperations in a NSOperationQueue. The firs is a download from a website (gets some json data) the next is parsing that data. This are dependent operations.
I don't understand how to link them together. If they are both allocated and in the queue, how do I transfer the json string to the operation that parses it? Is it a problem if this queue is inside another NSOperationQueue that executes an NSOperation that consists of the two mentioned previously?
All I could find is transfers of data to a delegate on the main thread (performSelectorOnMainThread), but I need all this operations to execute in the background.
Thanks.
Code:
NSDownload : NSOperation
- (instancetype)initWithURLString:(NSString *)urlString andDelegate:(id<JSONDataDelegate>)delegate
{
self = [super init];
if (self) {
_urlStr = urlString;
_delegate = delegate; /// this needs to be a NSOPeration
_receivedData = [NSMutableData dataWithCapacity:256];
}
return self;
}
#pragma mark - OVERRIDE
- (void)main
{
#autoreleasepool {
if (self.isCancelled) {
return;
}
NSURL *url = [NSURL URLWithString:self.urlStr];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
self.urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
}
}
#pragma mark - NSURLConnectionDataDelegate
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
if (self.isCancelled) {
[connection cancel];
self.receivedData = nil;
return;
}
[self.receivedData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if (self.isCancelled) {
self.receivedData = nil;
return;
}
// return data to the delegate
NSDictionary *responseDict = #{JSON_REQUESTED_URL : self.urlStr,
JSON_RECEIVED_RESPONSE : self.receivedData};
[(NSObject *)self.delegate performSelectorOnMainThread:#selector(didReceiveJSONResponse:) withObject:responseDict waitUntilDone:NO]; // ok to uses performSelector as this data is not for use on the main thread ???
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
// return error to the delegate
[(NSObject *)self.delegate performSelectorOnMainThread:#selector(didFailToReceiveDataWithError:) withObject:error waitUntilDone:NO];
}
#user1028028:
Use the following approach.
1) Maintain the operation queue reference that you are using to add DownloadOperation.
2) In connectionDidFinishLoading method, create ParseOperation instance, set the json data and add it to operation queue. Maintain ParseOperation strong reference variable in DownloadOperation and handling of cancelling of parsing operation through DownloadOperation interface.
3) After completed parsing call the UI functionality in main thread.
I hope this helps.
As lucianomarisi notes, it would usually be best to just have the first operation generate the second operation. This is usually simpler to manage. Operation dependencies aren't really that common in my experience.
That said, it's of course possible to pass data between operations. For instance, you could create a datasource property on the second operation. That would be the object to ask for its data; that object would be the first operation. This approach may require locking, though.
You can also create a nextOp property on the first operation. When it completes, it would call setData: on the second operation before exiting. You probably wouldn't need locking for this, but you might. In most cases it would be better for the first operation to just schedule the nextOp at this point (which again looks like lucianomarisi's answer).
The point is that an operation is just an object. It can have any methods and properties you want on it. And you can pass one operation to another.
Keep in mind that since an operation runs in the background, there's no reason you need to use the asynchronous interface to NSURLConnection. The synchronous API (sendSynchronousRequest:returningResponse:error: is fine for this, and much simpler to code. You could even use a trivial NSBlockOperation. Alternately, you can use the asynchronous NSURLConnection interface, but then you really don't need an NSOperation.
I also notice:
_receivedData = [NSMutableData dataWithCapacity:256];
Is it really such a small piece of JSON data? It's hard to believe that this complexity is worth it to move such a small parsing operation to the background.
(As a side note, unless you know precisely the size of the memory, there's not usually much benefit to specifying a capacity manually. Even then it's not always clear that it's a benefit. I believe NSURLConnection is using dispatch data under the covers now, so you're actually requesting a memory block that will never be used. Of course Cocoa also won't allocate it because it optimizes that out... the point is that you might as well just use [NSMutableData data]. Cocoa is quite smart about these kinds of things; you generally can only get in the way of its optimizations.)
As Rob said, unless you have any particular reason to use operations use the synchronized call. Then perform the selector on MainThread or on any other thread you need. Unless you want to separate the retrieval and parsing in separate operations or thread (explicitly).
Here is the code I was using for json retrieval and parsing:
-(BOOL) loadWithURL:(NSString*) url params: (NSDictionary*) params andOutElements:(NSDictionary*) jElements
{
NSError *reqError = nil;
NSString* urlStr = #"";//#"http://";
urlStr = [urlStr stringByAppendingString:url];
NSURL* nsURL = [NSURL URLWithString:urlStr];
//Private API to bypass certificate ERROR Use only for DEBUG
//[NSURLRequest setAllowsAnyHTTPSCertificate:YES forHost:[nsURL host]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL: nsURL
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
[request setHTTPMethod:#"POST"];
NSString *postString = #"";
if(params!=nil) {
NSEnumerator* enumerator = params.keyEnumerator;
NSString* aKey = nil;
while ( (aKey = [enumerator nextObject]) != nil) {
NSString* value = [params objectForKey:aKey];
//Use our own encoded implementation instead of above Apple one due to failing to encode '&'
NSString* escapedUrlString =[value stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
//Required to Fix Apple bug with not encoding the '&' to %26
escapedUrlString = [escapedUrlString stringByReplacingOccurrencesOfString: #"&" withString:#"%26"];
//this is custom append method. Please implement it for you -> the result should be 'key=value' or '&keyNotFirst=value'
postString = [self appendCGIPairs:postString key:aKey value:escapedUrlString isFirst:false];
}
}
//************** Use custom enconding instead !!!! Error !!!!! **************
[request setHTTPBody:[postString dataUsingEncoding:NSUTF8StringEncoding]];
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&reqError];
if(reqError!=nil) {
NSLog(#"SP Error %#", reqError);
return NO;
}
NSString *json_string = [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding];
//Handles Server Errors during execution of the web service that handles the call.
if([json_string hasPrefix:#"ERROR"] == YES){
NSLog(#"SP Error %#", lastError);
return NO;
}
//Very Careful!!!!!! Will stop for any reason.!!!!!!
//Handles errors from IIS Server that serves teh request.
NSRange range = [json_string rangeOfString:#"Runtime Error"];
if(range.location != NSNotFound) {
NSLog(#"SP Error %#", lastError);
return NO;
}
//Do the parsing
jElements = [[parser objectWithString:json_string error:nil] copy];
if([parser error] == nil) {
NSLog(#"Parsing completed");
} else {
jElements = nil;
NSLog(#"Json Parser error: %#", parser.error);
NSLog(#"Json string: %#", json_string);
return NO;
}
//Parsed JSON will be on jElements
return YES;
}

objective C process other events within while loop

Is there a way to force your application to process other events? I have a loop that is supposed to wait 10 seconds for a json response. The problem seems to be that the loop processes before the didReceiveData event can run.
JsonArray is an NSArray property.
Here is my code in Session.m:
-(BOOL)logIn
{
JsonArray = nil;
if (([Password class] == [NSNull class]) || ([Password length] == 0))
return NO;
if (([Username class] == [NSNull class]) || ([Username length] == 0))
return NO;
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:URL_ALL_PROPERTIES]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:10.0];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection start];
if (!connection)
{
return NO;
}
int time = CFAbsoluteTimeGetCurrent();
while (!JsonArray)
{
if (CFAbsoluteTimeGetCurrent() - time == 10)
{
NSLog(#"breaking loop");
break;
}
// process events here
}
if (!JsonArray) return NO;
NSLog(#"JsonArray not nil");
return YES;
}
didReceiveData handler:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(#"Data received");
NSError *e = nil;
JsonArray = nil;
JsonArray = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&e];
if (!JsonArray)
{
JsonArray = nil;
Connected = NO;
return;
}
Connected = YES;
}
Embrace the asynchronous nature of what you're trying to do. Remove the return from the login method and add a completion block instead. Store the completion block in a property (copy) and call it from connectionDidFinishLoading:.
typedef void (^MYCompletionHandler)(BOOL success);
#property (nonatomic, copy) void (^MYCompletionHandler)(bool *) completion;
- (void)loginUserWithCompletion:(MYCompletionHandler)completion {
self.completion = completion;
// start your login processing here
}
// when the login response is received
self. completion(##DID_IT_WORK##);
Your current code doesn't work because the main runloop isn't running while your hard loop is running, so the delegate methods are queued waiting to be processed. Don't try to work around it with the run loop, deal with the asynchronous nature properly.
The didReceiveData method will be called when the data has been received. If you need to process the data returned, do it in the delegate method.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// process your data in here
}
The logIn function has to finish before didReceiveData will be called. You cannot block for a response. That's the nature of NSURLConnection.

Losing responseData in NSURLConnection wrapper

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

How to test a class that makes HTTP request and parse the response data in Obj-C?

I Have a Class that needs to make an HTTP request to a server in order to get some information. For example:
- (NSUInteger)newsCount {
NSHTTPURLResponse *response;
NSError *error;
NSURLRequest *request = ISKBuildRequestWithURL(ISKDesktopURL, ISKGet, cookie, nil, nil);
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (!data) {
NSLog(#"The user's(%#) news count could not be obtained:%#", username, [error description]);
return 0;
}
NSString *regExp = #"Usted tiene ([0-9]*) noticias? no leídas?";
NSString *stringData = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSArray *match = [stringData captureComponentsMatchedByRegex:regExp];
[stringData release];
if ([match count] < 2)
return 0;
return [[match objectAtIndex:1] intValue];
}
The things is that I'm unit testing (using OCUnit) the hole framework but the problem is that I need to simulate/fake what the NSURLConnection is responding in order to test different scenarios and because I can't relay on the server to test my framework.
So the question is Which is the best ways to do this?
It's always tricky to test methods that call class methods like NSURLConnection sendSynchronousRequest
Here are a couple of options:
a) Use Matt Gallagher's invokeSupersequent macro to intercept the call. Your unit test would contain code like this:
#implementation NSURLConneciton (UnitTests)
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error {
if (someFlagYourTestUsesToInterceptTheCall) {
// return test NSData instance
}
return invokeSupersequent(request, &response, &error);
}
#end
Then you set someFlagYourTestUsesToInterceptTheCall to force it to intercept the call and return your test data.
b) Another alternative is to move that call into its own method in your class under test:
-(NSData *)retrieveNewsCount:(NSURLRequest *)request {
NSHTTPURLResponse *response;
NSError *error;
return [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
}
Then intercept that call in your test case using OCMock:
-(void)testNewsCount {
// instantiate your class
id myObject = ...;
id mock = [OCMockObject partialMockForObject:myObject];
[[[mock stub] andCall:#selector(mockNewsCount:) onObject:self] retrieveNewsCount:[OCMArg any]];
NSUInteger count = [myObject newsCount];
// validate response
...
}
// in the same test class:
-(NSData *)mockNewsCount:(NSURLRequest *)request {
// return your mock data
return mockData;
}
In this case, OCMock's stub:andCall:onObject:someMethod intercepts just this call to your object's method in order to inject some test data at test time.

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...:)