I'm using Apple Document to create an app.I succeed in connection to the server, but I receive 0 bytes from the server (no response data). I take the following steps:
I create a view-based App and add a property 'receivedData':
In ViewController.h:
#property (nonatomic, retain) NSMutableData *receivedData;
In ViewController.m:
#synthesize receivedData;
ViewController.m's action 'ViewDidLoad', I add:
receivedData = [NSMutableData alloc];
Add a button in the View and add action for it:
// create the request
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://..."]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// create the connection with the request
// and start loading the data
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
// Create the NSMutableData that will hold
// the received data
// receivedData is declared as a method instance elsewhere
receivedData=[[NSMutableData data] retain];
} else {
// inform the user that the download could not be made
}
When I debugging these codes, I find that receivedData returns 0 bytes. Any ideas about what goes wrong? A simple modify of my code will be appreciated.
Your code only creates the HTTP connection - the data will only be written to and available in receivedData after the delegate callbacks have been called by the framework (once the HTTP response is received). You can get more information and sample code from Apple's documentation
The answer is the same as it was the last time I answered it for you, over at How can I receive data from URL on iPhone?. I gave a detailed explanation-- did you read it?
Related
I am trying to set up a NSURLRequest to download a simple index.html with its externa style.css sheet but I am not quite sure how to do this.. I have only ever just formatted the URL of the request to the file I want.. but this has to be slightly different and I cannot find a good example of what I am trying to do.
this is my code so far:
#pragma mark - NSURLConnection methods
- (void)htmlRequest
{
// Create the request.
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.mywebsite.com/index.html"]
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:60.0];
// create the connection with the request
// and start loading the data
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];
} else {
// Inform the user that the connection failed.
NSLog(#"Connection Fail");
}
}
- (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)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// Append the new data to receivedData.
// receivedData is an instance variable declared elsewhere.
[receivedData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
// inform the developer of error type
}
// This method uses methodName to determin which Initalizer method to send the response data to in EngineResponses.m
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// EngineResponses *engineResponses = [EngineResponses sharedManager];
// [engineResponses GetManufacturers:receivedData];
NSString *myString = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
NSLog(#"%#", myString);
}
as you can see I am just calling index.html directly.. I would like to know how to format my request so i get the index.html as well as style.css
any help would be greatly appreciated.
I always create a new data structure,which has a -connection property and a -request property,like this
#interface connectionWrapper : NSObject
#property(retain) NSURLRequest *request
#property(retain) NSURLConnection *connection
by retaining this data structure in an mutable array, you can distinguish the connections in callback methods by iterate the array and compare each connectionWrapper instance's -connection property with the connection parameter the of the callback method, if they match(points to a same object), then you can retrieve the -request property of the connectionWrapper instance, then -url property of NSURLRequest instance.
as I'm not an native English speaker, I think code is a better tutor.
-(NSURLRequest*)getRequestByConnection:(NSURLConnection*)connection
{
for(connectionWrapper *w in theArrayContainingAllConnectionWrappers)
{
if(w == connection)
return w.request;
}
}
In callback method:
-(void)connection:(NSURLConnection*)connection didReceiveResponse(NSURLResponse*)response
{
NSURLRequest *request = [self getRequestByConnection:connection];
NSURL *url = [request url];
/*apply different approach to different url/*
}
PS:it's very sad that NSURLConnection don't have a -request property so that we can retrieve the request associated with the connection easily.
One way or another, you will have to make 2 requests. Even if you open a web page directly in a web browser, the browser will make a separate request for the CSS file referenced in the HTML it downloads. If your application needs both the HTML and the CSS file, then you want it to make 2 separate URL requests, first to get the HTML and then to get the CSS file.
Now, just because 2 requests need to be made, that doesn't mean you will always need to write the code that makes those 2 requests. It may be that libraries like the ones recommended by #Slee automatically take the results of a first request, parse them out, and make requests for any referenced CSS files. I have not worked with them so I am not sure what they support, or if any libraries will do this for you.
One thing you may want to consider is loading the HTML and CSS through a UIWebView rather than handling it all manually. UIWebView will attempt to load, parse, and render an HTML file into a UI component. In the process it will load referenced CSS and JavaScript files and apply them to its rendering. If you want to do anything special like intercept the calls it makes to load the CSS file(s), you can implement the UIWebViewDelegate protocol and set the delegate of the the UIWebView. Within that delegate you can implement the -webView:shouldStartLoadWithRequest:navigationType: method to be notified when the web view is loading the CSS file. You can use the call to that method to look at the request that is being issued for the CSS and do something else interesting with the request.
do you know the name of the .css file?
If so I would just make 2 requests otherwise you will have to write a parser to look for the link to the css and make a second request anyways.
I'd also suggest looking into a library to handle the downlading of stuff - lot's of great libraries that can do the heavy lifting for you with advanced features.
Here's 3 I have used:
http://blog.mugunthkumar.com/coding/ios-tutorial-advanced-networking-with-mknetworkkit/
https://github.com/tonymillion/TMHTTPRequest
https://github.com/pokeb/asi-http-request
Here is my actual issue, as some suggested I want to write a class for handling the multiple download progress in a UITableView. I have no idea how to write a class for this, can somebody help with some tips or ideas?
The group to look at is NSURLRequest and NSURLConnection. The former let's you specify the request (URL, http method, params, etc) and the latter runs it.
Since you want to update status as it goes (I answered a sketch of this in your other question, I think), you'll need to implement the NSURLConnectionDelegate protocol that hands over chunks of data as it arrives from the connection. If you know how much data to expect, you can use the amount received to calculate a downloadProgress float as I suggested earlier:
float downloadProgress = [responseData length] / bytesExpected;
Here's some nice looking example code in SO. You can extend for multiple connections like this...
MyLoader.m
#interface MyLoader ()
#property (strong, nonatomic) NSMutableDictionary *connections;
#end
#implementation MyLoader
#synthesize connections=_connections; // add a lazy initializer for this, not shown
// make it a singleton
+ (MyLoader *)sharedInstance {
#synchronized(self) {
if (!_sharedInstance) {
_sharedInstance = [[MyLoader alloc] init];
}
}
return _sharedInstance;
}
// you can add a friendlier one that builds the request given a URL, etc.
- (void)startConnectionWithRequest:(NSURLRequest *)request {
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
NSMutableData *responseData = [[NSMutableData alloc] init];
[self.connections setObject:responseData forKey:connection];
}
// now all the delegate methods can be of this form. just like the typical, except they begin with a lookup of the connection and it's associated state
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSMutableData *responseData = [self.connections objectForKey:connection];
[responseData appendData:data];
// to help you with the UI question you asked earlier, this is where
// you can announce that download progress is being made
NSNumber *bytesSoFar = [NSNumber numberWithInt:[responseData length]];
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
[connection URL], #"url", bytesSoFar, #"bytesSoFar", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"MyDownloaderDidRecieveData"
object:self userInfo:userInfo];
// the url should let you match this connection to the database object in
// your view controller. if not, you could pass that db object in when you
// start the connection, hang onto it (in the connections dictionary) and
// provide it in userInfo when you post progress
}
I wrote this library to do exactly that. You can checkout the implementation in the github repo.
NSURL *URL = [NSURL URLWithString:#"http://www.stackoverflow.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
With code as simple as that, I can load a webpage in my application. I don't have to worry about retaining or releasing the NSURLConnection, it will autorelease when it's done loading.
I'm creating some sort of wrapper around NSURLConnection, JSONConnection. It allows me to load a JSON value from a webpage and automatically parse that in a NSDictionary. Right now, I have to use it like this:
JSONConnection *tempJSONConnection = [[JSONConnection alloc] initWithURLString:#"http://www.stackoverflow.com" delegate:self];
self.JSONConnection = tempJSONConnection;
[tempJSONConnection release];
Then, when it's done loading, I call self.JSONConnection = nil;.
What I want, is to do this:
JSONConnection *connection = [JSONConnection connectionWithURLString:#"http://www.stackoverflow.com" delegate:self];
I know how to create this method. I just don't know how to keep connection alive when the runloop is finished and the autorelease pool is drained, and make sure connection is deallocated when it's done loading. In other words, I don't how to duplicate the exact behavior of NSURLConnection.
To all intents and purposes, from the outside, NSURLConnection effectively retains itself. This was either done by sending
[self retain];
when starting the connection and then
[self release];
when finished and after informing the delegate; or it was done by placing itself in a pool of currently open connections and removing it from that pool on completion.
You don't actually have to do any of this. NSURLConnection retains its delegate, so your JSON connection class should create an NSURLConnection passing itself as the NSURLConnection's delegate. That way it will live at least as long as the NSURLConnection. It should parse the JSON into a dictionary in the method -connectionDidFinishLoading: and pass the dictionary on to its delegate before returning. After returning the NSURLConnection will release and possibly deallocate itself and also release your JSON connection.
Someone should trac connection's live time in any case. It is a bad solutions to trac it inside the connection.
IMO the right way to do it is use singleton class to perform connections
#protocol JSONDataProviderDelegate <NSObject>
- (void) JSONProvider:(JSONDataProvider*) provider didLoadJSON:(JSONObject*) object;
- (void) JSONProvider:(JSONDataProvider*) provider didFainWithError:(NSError*) error;
#end
#interface JSONDataProvider : NSObject
+ (void) provideJSON:(NSURL*) url delegate:(id<JSONDataProviderDelegate>) delegate;
+ (void) removeDelegate:(id<JSONDataProviderDelegate>delegate);
#end
Usage:
- (void) onSomeEvent
{
[JSONDataProvider provideJSON:[NSURL URLWithString:#"http://example.com/test.json"] delegate:self];
}
- (void) JSONProvider:(JSONDataProvider*) provider didLoadJSON:(JSONObject*) object
{
NSLog(#"JSON loaded: %#", object);
}
- (void) dealloc
{
[JSONDataProvider removeDelegate:self];
[super dealloc];
}
I'm developing an iPhone application where the data comes from a webservice that returns a json file. I'm using the following command in the viewDidLoad of the UITableViewController where I want to bind the data:
responseData = [[NSMutableData data] retain];
NSURLRequest *request = [NSURLRequest requestWithURL:
[NSURL URLWithString:#"webservice url"]];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
and then I get the data from the json result into a NSDictionary in the connectionDidFinishedLodading Delegate.
The problem is that the numberOfRowsInSection delegate from UITableView executes before the connection finishes downloading data, which makes my table view with zero rows and doesn't show anything.
What am I doing wrong? Did I misunderstand the concept of the viewDidLoad and should be loading this data elsewhere?
Call reloadData on the tableView when you are done processing the data.
[self.tableView reloadData];
This triggers all UITableViewDatasource methods anew.
// Create the request.
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.apple.com/"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// create the connection with the request
// and start loading the data
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] retain];
} else {
// Inform the user that the connection failed.
}
Since we dont' "own" receivedData by calling retain on it, aren't we leaking memory?
When are you supposed to release connection and receivedData if at all?
1/ About the connection, we use the delegate pattern to handle the memory management of this. You alloc init and set the delegate in one method. And then when the connection callback you like:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[connection release];
}
or you can release the connection in any other delegate methods. This is one of reasons why they pass you back the connection. You will meet this delegate pattern a lot in iPhone like UIImagePickerController (just for another example), especially in networking problems when you have to wait until the network finish to release
2/ From the comment,
// Create the NSMutableData to hold the received data.
// receivedData is an instance variable declared elsewhere.
So, that is easy to answer, because receivedData is an instance variable, you should and can release it in dealloc method. Another choice for you is to declare a #property (nonatomic, retain) for it and then it will make sure no memory leak if you set the receivedData multiple times
This code owns both the connection and the data. The connection is created with alloc/init, requiring a release later, and the data is retained, so that too will require a release.