I am trying to create a simple NSURLConnection to communicate with a server using a GET request. Connection works well, but delegates methods of NSURLConnection are never called..
Here is what am doing:
NSString *post = [NSString stringWithFormat:#"key1=%#&key2=%#&key3=%f&key4=%#", val1, val4, val3, val4];
NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease] ;
[request setURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://www.domain.com/demo/name/file.php?%#", post]]];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection start];
Have implemented the following delegate methods, but none of them is called..
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
NSLog(#"did fail");
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
NSLog(#"did receive data");
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
NSLog(#"did receive response ");
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSLog(#"did finish loading");
[connection release];
}
Am I missing something?
Try running the operation on main thread:
NSURLConnection * connection = [[NSURLConnection alloc]
initWithRequest:request
delegate:self startImmediately:NO];
[connection scheduleInRunLoop:[NSRunLoop mainRunLoop]
forMode:NSDefaultRunLoopMode];
[connection start];
Are you calling this on a background thread? If you are performing this on a background thread, the thread is probably exiting before the delegates can be called.
Try to check length for the received response it should not getting 0 byte of data.
Apart from checking if the request is called from the main thread, you can check if you give back execution time to the system (if you exit "main").
I had some test code that would stay in a loop until the delegate was called : it would never be called, because the system needs to do stuff in order for the delegate to be called, in the main thread.
Related
I have implemented an NSURLConnection that sends a request to a server and receives some data back which is stored in an NSMutableData object. These are the methods that I implemented as part of NSURLConnectionDelegate:
-(void)upLoadBook:(NSMutableDictionary *)theOptions{
NSMutableString *theURL = [[NSMutableString alloc] initWithString:#"theURL"];
[theURL appendFormat:#"&Title=%#&Author=%#&Price=%#", [theOptions objectForKey:#"bookTitle"],
[theOptions objectForKey:#"bookAuthor"],
[theOptions objectForKey:#"bookPrice"]];
[theURL appendFormat:#"&Edition=%#&Condition=%#&Owner=%#", [theOptions objectForKey:#"bookEdition"],
[theOptions objectForKey:#"bookCondition"],
_appDel.userID];
NSLog(#"%#\n", theURL);
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:theURL]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:10.0];
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
// Create the NSMutableData to hold the received data.
// receivedData is an instance variable declared elsewhere.
receivedData = [NSMutableData data];
}
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[receivedData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse
*)response
{
// This method is called when the server has determined that it
// has enough information to create the NSURLResponse.
// It can be called multiple times, for example in the case of a
// redirect, so each time we reset the data.
// receivedData is an instance variable declared elsewhere.
[receivedData setLength:0];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// do something with the data
// receivedData is declared as a method instance elsewhere
//Receives a response after book has been uploaded (Preferably a Book ID...)
responseString = [[NSString alloc] initWithData:receivedData
encoding:NSUTF8StringEncoding];
NSLog(#"Response String: %#", responseString);
[_options setValue:responseString forKey:#"bookID"];
[self performSegueWithIdentifier:#"UploadSuccessSegue" sender:self];
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Whoops." message:#" No internet
connection.\n Please make sure you have a connection to the internet."
delegate:self cancelButtonTitle:#"Ok"
otherButtonTitles: nil];
[alert show];
}
The function uploadBook seems to be called,however, I never get to didFinishLoading and didReceiveData never receives any data. What could be a possible problem. Any hints or clues would be much appreciated.
You need to add your NSURLConnection to the current run loop or a separate one (such as one you set up in a separate thread). The delegate methods do need to get CPU time, after all.
Looking at this related question's accepted answer, it can also be done via Grand Central Dispatch:
dispatch_async(dispatch_get_main_queue(), ^{
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
[conn start];
});
One thing for sure is that you should %-escape your list of parameter before trying to send the request.
You can use stringByAddingPercentEscapesUsingEncoding for that purpose:
NSMutableString *theURL = [[NSMutableString alloc] initWithString:#""];
[theURL appendFormat:#"&Title=%#&Author=%#&Price=%#", [theOptions objectForKey:#"bookTitle"],
[theOptions objectForKey:#"bookAuthor"],
[theOptions objectForKey:#"bookPrice"]];
[theURL appendFormat:#"&Edition=%#&Condition=%#&Owner=%#", [theOptions objectForKey:#"bookEdition"],
[theOptions objectForKey:#"bookCondition"],
_appDel.userID];
theURL = [NSStringWithFormat:#"YOUR_URL_HERE?",[theURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
Please, note that I refactored your code with the minimum number of changes to get the result. You can find better refactorizations for sure.
Here is a sample that works from one of my projects:
NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"http://www.brayden.me/analytics/device.php"]];
[urlRequest setHTTPMethod:#"POST"];
NSMutableString *postParams = [NSMutableString string];
[postParams appendFormat:#"session=%#&", analyticsSession];
[postParams appendFormat:#"device=%#&", device];
[postParams appendFormat:#"system=%#&", csystem];
[postParams appendFormat:#"version=%#&", version];
[postParams appendFormat:#"launch=%f&", totalLaunchTime];
if([Analytics_Location location].latitude && [Analytics_Location location].longitude) {
[postParams appendFormat:#"latitude=%#&", [Analytics_Location location].latitude];
[postParams appendFormat:#"longitude=%#&", [Analytics_Location location].longitude];
}
[urlRequest setHTTPBody:[postParams dataUsingEncoding:NSUTF8StringEncoding]];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self startImmediately:YES];
[connection start];
Make sure your header method also uses . The code of mine should at least show you how to properly format the request, as I can verify this does receive data from a PHP call of mine.
I have looked at NSURLConnectionDelegate connection:didReceiveData not working already, but there didn't seem to be any good result from that, so I am curious why I am not able to get any data.
I put in breakpoints in didReceiveResponse and didReceiveData.
It does print out "connection succeeded", so I know that the connection is started.
I am using ARC for memory management.
- (void)load {
request = [NSMutableURLRequest requestWithURL:myURL
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:60];
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if (conn) {
[conn start];
NSLog(#"connection succeeded, %s", [myURL description]);
responseData = [NSMutableData data];
} else {
NSLog(#"connection failed");
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
responseData = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[responseData appendData:data];
}
UPDATE:
To see how I test this look at Asynchronous unit test not being called by SenTestCase.
I did implement the two methods mentioned by jonkroll, in his answer, I just didn't show them, but, they also aren't being called.
I had added [conn start] only because it wasn't working, and I was hoping that may solve it, but no such luck.
When you declare your connection like this:
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
You are creating a local pointer. When your method completes, since it was the last strong reference to the NSURLConnection, ARC releases it. You need to use a strong ivar (and/or) property to hold a strong reference to the NSURLConnection you create.
Edit
Here is basic sample of code that I tested in a sample project. Give it a run. Verbose logging helps.
#implementation <#Your class here#> {
// With ARC ivars are strong by default
NSMutableData *_downloadedData;
NSURLConnection *_connection;
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
NSHTTPURLResponse *realResponse = (NSHTTPURLResponse *)response;
if (realResponse.statusCode == 200){
// Really any 2** but for example
_downloadedData = [[NSMutableData alloc] init];
NSLog(#"Good response");
} else {
NSLog(#"Bad response = %i",realResponse.statusCode);
}
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
if (connection == _connection){
[_downloadedData appendData:data];
NSLog(#"Getting data...");
}
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
if (connection == _connection){
_connection = nil;
NSLog(#"We're done, inform the UI or the delegates");
}
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
_connection = nil;
NSLog(#"Oh no! Error:%#",error.localizedDescription);
}
- (void)load {
NSURL *url = [NSURL URLWithString:#"http://www.google.com/"];
NSURLRequest *request = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:60];
// Assign strong pointer to new connection
_connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
NSLog(#"Connection was initialized? = %#",(!!_connection)?#"YES":#"NO");
}
#end
The NSURLConnection method initWithRequest starts an asynchronous request for data from a url. Because the request is done asynchronously you can't expect to work with the response in the same method in which the request is invoked. Instead you need to do so in the NSURLConnection's delegate callback methods. You have already implemented didReceiveResponse: and didReceiveData:, but there are a couple others that will be useful to you.
If you want to look at the contents of the response you should do so in connectionDidFinishLoading:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// response is complete, do something with the data
NSLog(#"%#", responseData);
}
The fact that your code prints out "connection succeeded" doesn't really mean that the request was successful, only that the NSURLConnection object was created successfully. To test whether there was a problem with the connection you can implement the delegate method connection:didFailWithError:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(#"Connection failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
Also there is no need to call [conn start]. The request will be started automatically when you call initWithRequest:
I suggest reading Apple's documentation on Using NSURLConnection for more details.
I would like to know how do I get a return value 1 or 0 only.... back from an URL request asynchronously.
currently I do it in this way:
NSString *UTCString = [NSString stringWithFormat:#"http://web.blah.net/question/CheckQuestions?utc=%0.f",[lastUTCDate timeIntervalSince1970]];
NSLog(#"UTC String %#",UTCString);
NSURL *updateDataURL = [NSURL URLWithString:UTCString];
NSString *checkValue = [NSString stringWithContentsOfURL:updateDataURL encoding:NSASCIIStringEncoding error:Nil];
NSLog(#"check Value %#",checkValue);
this works, however it is blocking my main thread till I got a reply back from the URL, how do I set it so it will do it in a another thread instead of the main thread ?
EDIT: ANSWER
I end upcalling my function with this, it works well :)
[self performSelectorInBackground:#selector(shouldCheckForUpdate) withObject:nil];
you can use NSURLConnection class
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
[[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease];
and handle its response and errors using its delegate methods.
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
You can find implementation of NSURLConnection
Apple docs: Using NSURLConnection
How To Use iOS NSURLConnection By Example
Edit: Although NSURLConnection is provided by apple is more recommended way of placing URL request. But I found AFNetworking library very time saving, easy to implement and robust yet simple as third party implementation. You should give it a try.
try this :
.h:
NSMutableData *responseData;
.m:
- (void)load
{
NSURL *myURL = [NSURL URLWithString:#"http://www.example.com"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:myURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
responseData = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[responseData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
[responseData release];
[connection release];
[textView setString:#"Unable to fetch data"];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"Succeeded! Received %d bytes of data",[responseData
length]);
NSString *txt = [[[NSString alloc] initWithData:responseData encoding: NSASCIIStringEncoding] autorelease];
}
Use NSURLConnection and make your request.
Then you may start synchronous or asynchronous connection with NSURLConnection's methods :
Loading Data Synchronously
+ sendSynchronousRequest:returningResponse:error:
Loading Data Asynchronously
+ connectionWithRequest:delegate:
– initWithRequest:delegate:
– initWithRequest:delegate:startImmediately:
– start
Check the NSURLConnection class in Apple Developer API Reference.
Shamelessly copy from https://gist.github.com/knmshk/3027474. All credits go to https://gist.github.com/knmshk.
xmlData = [[NSMutableData alloc] init];
NSURL *url = [NSURL URLWithString:
#"http://forrums.bignerdranch.com/smartfeed.php?"
#"limit=NO_LIMIT&count_limit20&sort_by=standard&"
#"feed_type=RSS2.0&feed_style=COMPACT"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[NSURLConnection sendAsynchronousRequest:request
queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
if (error) {
xmlData = nil;
NSLog(#"error:%#", error.localizedDescription);
}
[xmlData appendData:data];
}];
There is an example in the iOS XCode documentation called LazyTableImages. This does an asynchronous URL as well as asynchronous image load into UITableView cells displayed on the screen after scrolling stops. Excellent example of protocols, asynchronous data handling, etc.
I saw there is a method for synchronous, like if I wanted to do something like:
-(IBAction)doNSURLConnSync {
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSError *error = nil;
NSURLResponse *response = nil;
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
}
How does it perform differently than if I did asynchronous:
-(IBAction)doNSURLConnASync {
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
delegate:self];
if (connection) {
responseData = [[NSMutableData alloc] init];
[webview loadHTMLString:#"" baseURL:nil];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}
else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:#"Network error occured"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
[alert release];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSLog(#"%s", __FUNCTION__);
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[responseData appendData:data];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[connection release]; // got passed in as a param. we are done with it now.
[webview loadData:responseData
MIMEType:nil
textEncodingName:nil
baseURL:nil];
[responseData release];
}
Also, with my doNSURLConnSync method, I am just trying to load a UIWebView. Is there a reason why it doesn't? The button just sits there and stays highlighted, while it tries to access the webpage, but does nothing in the end, compared to the asynchronous version.
Also, for networkactivityindicator in my asynchronous code, I wanted to set my UIWebView to blank, have the indicator on while my webview loads, and then turn off the network activity indicator once the page loads. However, if I delete the loadHTMLString method, the network activity indicator works as it's supposed to, but with the loadHTMLString, the UIWebView goes blank, but the network activity indicator does not. Any thoughts? Thanks.
First, for the syncrhonous:
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error
This is the method signature, when you call the synchronous request, it wil return the data for you to display on the UIWebView and you have to call the UIWebView to display the data. However, the synchronous calling will block your UI until all the data come back. So, be careful with UX.
NSUrlConnection sendSynchronousRequest
For the asynchronous, it will not block your UI, user can still do everything they want with it, like go back to the previous screen. So, usually, it is recommended for big and long network
I don't know why it doesn't show your indicator. But why do you need this line : [webview loadHTMLString:#"" baseURL:nil]; . You only need to call it after you got your HTML response
A synchronous request ties up the main thread, which you should reserve for UI widget updates.
Doing an asynchronous request on a background thread frees up the main thread to update the UI.
Pull your UI update code (indicator view and web view) into separate methods, calling them on the main thread with -performSelectorOnMainThread:withObject:waitUntilDone:
[self performSelectorOnMainThread:#selector(updateWebview) withObject:nil waitUntilDone:YES];
Have a look to this code snippet:-
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[webData setLength: 0];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(#"Recieving Data...");
[webData appendData:data];
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(#"ERROR with theConenction");
[connection release];
[webData release];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"DONE. Received Bytes: %d", [webData length]);
NSLog(theXML);
}
I am calling a SOAP web service.There are no errors or warnings displayed in my code.
When I hit the web service through safari it works fine. But the problem arises when I try
hit it through my codes.
Everything works fine but the connection:didRecieveData does not gets called.
Thus, I get no data in the webData variable. This webData is a NSMutableData object.
The problem seems to be silly but any one with any answers ....
Thank You All.
I suspect you are having a memory management issue. I could be mistaken on this, but I believe that even:
NSURLConnection* connection=[[NSURLConnection alloc] initWithRequest:request delegate:self];
won't work, because connection will be released at the end of the containing method, when connection goes out of scope. Make sure NSURLConnection *connection and NSMutableData *data are declared as member variables where ever you are doing this, and that you alloc and init them appropriately. My code usually looks like:
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:30.0];
// cancel any old connection
if(connection) {
[connection cancel];
[connection release];
}
// create new connection and begin loading data
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if(connection) {
// if the connection was created correctly, release old data (if any), and alloc new
[data release];
data = [[NSMutableData data] retain];
}
Also, release the connection and data in dealloc. For good measure, release and set them to nil at the very end of didFailWithError and didFinishLoading:
[connection release];
connection = nil;
[data release];
data = nil;
Good luck; I've done this a million times, let me know if you cannot get it working.
You don't happen to be calling the NSConnection in a thread do you? If you are then what's happening is that the thread is terminating before NSConnection and its delegates have finished so it'll just bomb out without an error.
A workaround for this is in this thread
You're not getting any error messages in didFailWithError either? Kind of a silly suggestion, but are you sure you're setting the proper NSURLConnection delegate?
NSURLConnection* connection=[[NSURLConnection alloc] initWithRequest:request delegate:self];
Sometimes it's something small like that.
Another idea is to drop in a toolkit like ASIHTTPRequest and see if it works going through them.
There also could be problems, if are trying to start NSURLConnection from another Thread.
Please call method [connection start] on main thread, if you have not customized Run Loop for it.