Whenever I create an NSURLConnection in a class I have, it always connects to the first URL connected to by that class. It has an ivar conn that the NSURLConnection is stored in, and here is the method that connects:
-(void)getMoreProblems
{
problemsPage++;
NSURL *url=[NSURL URLWithString:[NSString stringWithFormat:#"http://projecteuler.net/problems;page=%d",problemsPage]];
NSURLRequest *req=[NSURLRequest requestWithURL:url];
NSLog(#"%p",conn);
conn=[[NSURLConnection alloc] initWithRequest:req delegate:self];
NSLog(#"%p",conn);
}
I have checked by NSLoging the URL's description and the Connection's pointer that they are different, as well as telling the UIApplication to load the URL in safari. As far as I can tell, It tries to load the right page. I also tried both POST and GET, but it didn't make a difference. What might be causing this?
EDIT FOR ANYONE LOOKING AT THIS WITH A SIMILAR PROBLEM:
My problem ended up being that I did not reinitialize the NSMutableData I stored the connection data in after each page loaded.
This isn't really an answer, but it's too long for a comment. I can't see anything wrong with the code that you posted. I pasted your code for getMoreProblems into a new project and added the delegate methods necessary to look at the results -- as far as I can tell it worked fine. I can see in the resulting string, the problem numbers starting with 1 on the first page I receive (from the first call to getMoreProblems) and starting with problem 51 on the second call to getMoreProblems. The only thing I added to your getMoreProblems method was the if-else clause at the end. HEre is the code I used:
#synthesize window = _window,receivedData;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
problemsPage = 0;
[self getMoreProblems];
}
-(void)getMoreProblems {
problemsPage++;
NSURL *url=[NSURL URLWithString:[NSString stringWithFormat:#"http://projecteuler.net/problems;page=%d",problemsPage]];
NSURLRequest *req=[NSURLRequest requestWithURL:url];
NSLog(#"%p",conn);
conn=[[NSURLConnection alloc] initWithRequest:req delegate:self];
NSLog(#"%p",conn);
if (conn) {
self.receivedData = [NSMutableData data];
} else {
NSLog(#"The Connection Failed");
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSLog(#"%#",response.URL);
[self.receivedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(#"In connection:didReceiveData:");
[self.receivedData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(#"Succeeded! Received %lu bytes of data",[receivedData length]);
NSString *page = [[NSString alloc] initWithData:self.receivedData encoding:NSUTF8StringEncoding];
NSLog(#"%#",page);
[self performSelector:#selector(getMoreProblems) withObject:nil afterDelay:5];
}
So, I can't reproduce your problem -- I'm guessing it lies elsewhere in some code that you didn't post.
Related
I am relatively new to objective-c but struggling with delegates when it comes to NSURLConnection. Below I have an implementation file api.m
Elsewhere in my viewcontrollers I call this api object with the method getGroups and the purpose here is to return the number of groups found when the API request is made. I can see the data in the didReceiveData but how can I get this data back into my getGroups so that I can access it in my viewController?
In my view controller I have something like:
NSInteger *numGroups = [apiRequest getGroups];
and in my api.m implementation file I have the following. Again everything works I am just not sure how to return the data from didReceiveData back so I can access it in getGroups method.
#import "API.h"
#import "Constants.h"
#import "JSONParser.h"
#implementation API
#synthesize user, url, receivedData
-(NSInteger)getGroups {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10];
[request setValue:APIKEY forHTTPHeaderField:#"apikey"];
[request setHTTPMethod:#"GET"];
[request setURL:url];
NSURLConnection *myConnection;
myConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
//How do I access what was append'd in receivedData below
return 2;
}
/* ----------------------------------------------------------------------------------------
NSURLConnection Delegates
------------------------------------------------------------------------------------------- */
// Check the response code that was returned
- (NSInteger)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
return [httpResponse statusCode];
}
// Take a peak at the data returned.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(#"DATA: %#", [data description]);
//How to get this information back up into the getGroups method
[receivedData appendData: data];
}
// Close the connection
- (void)connectionDidFinishLoading:(NSURLConnection*)connection {
NSLog(#"Connection Closed.");
}
#end
What you want to do is in your ViewController that is calling the API set the API's delegate to self. Then you need to add those delegate methods inside your ViewController, not use them out of the API. That way when the NSURLConnection tries to call one of the delegate methods it will be accessible within youre ViewController. You also want to make sure you add the delegate protocol inside your ViewController's .h file as well.
As a quick example your VC.h file will contain the following:
#interface ViewController : UIViewController <NSURLConnectionDataDelegate>
Then in your VC.m file you'd have the following methods:
/* ----------------------------------------------------------------------------------------
NSURLConnection Delegates
------------------------------------------------------------------------------------------- */
// Check the response code that was returned
- (NSInteger)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
return [httpResponse statusCode];
}
// Take a peak at the data returned.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(#"DATA: %#", [data description]);
//How to get this information back up into the getGroups method
[receivedData appendData: data];
}
// Close the connection
- (void)connectionDidFinishLoading:(NSURLConnection*)connection {
NSLog(#"Connection Closed.");
}
Now when your NSURLConnection tries to call didReceiveData it will be called inside your ViewController, not in the API.
As a side note I whole heartedly recommend taking #SK9's advice and make this an Async call to abstract it from the main thread.
NSURLConnection's sendSynchronousRequest:returningResponse:error:, see here, will return a data object. Do be sure you're happy to block the current thread like this. I'd prefer for this to be an asynchronous request, with a completion block to handle the return. More details on the page I referred to, but do make reading up on blocks a priority if this is new. The Short Practical Guid to Blocks might help.
i'm quite a beginner at cocoa an objective-c - so please forgive me a possibly trivial question.
I'm currently working on an XCODE-Project which is fetching some data via NSJSONSerialization and store it for further work.
At this step I'm going to encapsulate the progress of fetching the data into an class which has some setter for the needed parameters (url to fetch from and the layer which should be parsed into an array). In order to use this procedure im creating the method inside this class which creates a connection and a request and returns the array which should contain the data. After some tests I tried to create an instance of this class and called the method which starts to fetch the data.
My problem is, that after calling the method data_array from my new instance "block_stats"
and store the data in an array of the same type - the array is empty
table_data = [block_stats data_array];
The reason of this behavior is that the usage of the methods in (didReceiveResponse,didReceiveData,connectionDidFinishLoading) are working asynchron and the return of the data_array was done before the download was finished.
the method inside the class which contains the downloading part:
- (NSMutableArray *)data_array
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
if(data_array)
{
[data_array removeAllObjects];
} else {
data_array = [[NSMutableArray alloc] init];
}
NSURLRequest *request = [NSURLRequest requestWithURL:data_url];
connection = [NSURLConnection connectionWithRequest:request delegate:self];
if(connection)
{
webdata = [[NSMutableData alloc]init];
}
return data_array;
}
the IBAction in another view which creates the instance and calls the method to fetch the data
- (IBAction)refresh:(UIBarButtonItem *)sender {
KDJSONparser *block_stats = [[KDJSONparser alloc]init];
[block_stats setURL:[NSURL URLWithString:#"*JSONDATA_URL*"]];
[block_stats setLayer:#"blocks"];
table_data = [block_stats data_array];
}
I would be very glad if anyone could give some advice. It would be very nice if it would be as easy as possible to understand for me. Thanks in advance!
The solution to your problem lies in delegation (As the word suggests you will be appointing some one else to take action when a situation presents itself.)
You have already used this in the following piece of your code.
NSURLRequest *request = [NSURLRequest requestWithURL:data_url];
connection = [NSURLConnection connectionWithRequest:request delegate:self];
Here when you have set self as the delegate for your NSURLConnection you are telling the compiler to send you any appropriate messages related to the connection. These messages include didReceiveResponse,didReceiveData,connectionDidFinishLoading.
So let's implement these methods in your class and they will look something like this.
KDJSONParser.m
- (NSMutableArray *)fetchData
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSURLRequest *request = [NSURLRequest requestWithURL:data_url];
connection = [NSURLConnection connectionWithRequest:request delegate:self];
if(connection)
{
webdata = [[NSMutableData alloc]init];
}
}
- (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.
[webdata setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// Append the new data to receivedData.
// receivedData is an instance variable declared elsewhere.
[webdata appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[connection release];
//Parse the webdata here. Your array must be filled now.
[self parseTheJSONData];
}
-(void)parseTheJSONData
{
//Do your parsing here and fill it into data_array
[self.delegate parsedArray:data_array];
}
And in your other class add this line in you refresh method
block_stats.delegate = self;
and implement
-(void)parsedArray:(NSMutableArray *)data_array;
I've read through tons of messages saying the same thing all over again : when you use a NSURLConnection, delegate methods are not called. I understand that Apple's doc are incomplete and reference deprecated methods, which is a shame, but I can't seem to find a solution.
Code for the request is there :
// Create request
NSURL *urlObj = [NSURL URLWithString:url];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:urlObj cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30];
[request setValue:#"gzip" forHTTPHeaderField:#"Accept-Encoding"];
if (![NSURLConnection canHandleRequest:request]) {
NSLog(#"Can't handle request...");
return;
}
// Start connection
dispatch_async(dispatch_get_main_queue(), ^{
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES]; // Edited
});
...and code for the delegate methods is here :
- (void) connection:(NSURLConnection *)_connection didReceiveResponse:(NSURLResponse *)response {
NSLog(#"Receiving response: %#, status %d", [(NSHTTPURLResponse*)response allHeaderFields], [(NSHTTPURLResponse*) response statusCode]);
self.data = [NSMutableData data];
}
- (void) connection:(NSURLConnection *)_connection didFailWithError:(NSError *)error {
NSLog(#"Connection failed: %#", error);
[self _finish];
}
- (void) connection:(NSURLConnection *)_connection didReceiveData:(NSData *)_data {
[data appendData:_data];
}
- (void)connectionDidFinishDownloading:(NSURLConnection *)_connection destinationURL:(NSURL *) destinationURL {
NSLog(#"Connection done!");
[self _finish];
}
There's not a lot of error checking here, but I've made sure of a few things :
Whatever happens, didReceiveData is never called, so I don't get any data
...but the data is transfered (I checked using tcpdump)
...and the other methods are called successfully.
If I use the NSURLConnectionDownloadDelegate instead of NSURLConnectionDataDelegate, everything works but I can't get a hold on the downloaded file (this is a known bug)
The request is not deallocated before completion by bad memory management
Nothing changes if I use a standard HTML page somewhere on the internet as my URL
The request is kicked off from the main queue
I don't want to use a third-party library, as, ultimately, these requests are to be included in a library of my own, and I'd like to minimize the dependencies. If I have to, I'll use CFNetwork directly, but it will be a huge pain in the you-know-what.
If you have any idea, it would help greatly. Thanks!
I ran into the same problem. Very annoying, but it seems that if you implement this method:
- (void)connectionDidFinishDownloading:(NSURLConnection *)connection destinationURL:(NSURL *)destinationURL
Then connection:didReceiveData: will never be called. You have to use connectionDidFinishLoading: instead... Yes, the docs say it is deprecated, but I think thats only because this method moved from NSURLConnectionDelegate into NSURLConnectionDataDelegate.
I like to use the sendAsynchronousRequest method.. there's less information during the connection, but the code is a lot cleaner.
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
if (data){
//do something with data
}
else if (error)
NSLog(#"%#",error);
}];
From Apple:
By default, a connection is scheduled on the current thread in the
default mode when it is created. If you create a connection with the
initWithRequest:delegate:startImmediately: method and provide NO for
the startImmediately parameter, you can schedule the connection on a
different run loop or mode before starting it with the start method.
You can schedule a connection on multiple run loops and modes, or on
the same run loop in multiple modes.
Unless there is a reason to explicitly run it in [NSRunLoop currentRunLoop],
you can remove these two lines:
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[connection start];
or change the mode to NSDefaultRunLoopMode
NSURLConnection API says " ..delegate methods are called on the thread that started the asynchronous load operation for the associated NSURLConnection object."
Because dispatch_async will start new thread, and NSURLConnection will not pass to that other threat the call backs, so do not use dispatch_async with NSURLConnection.
You do not have to afraid about frozen user interface, NSURLConnection providing only the controls of asynchronous loads.
If you have more files to download, you can start some of connection in first turn, and later they finished, in the connectionDidFinishLoading: method you can start new connections.
int i=0;
for (RetrieveOneDocument *doc in self.documents) {
if (i<5) {
[[NSURLConnection alloc] initWithRequest:request delegate:self];
i++;
}
}
..
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
ii++;
if(ii == 5) {
[[NSURLConnection alloc] initWithRequest:request delegate:self];
ii=0;
}
}
One possible reason is that the outgoing NSURLRequest has been setup to have a -HTTPMethod of HEAD. Quite hard to do that by accident though!
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.
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.