I am working on a app and try to display part of a web page.
My idea is first, get page source then parse it!
I already found a useful HTML parser for it but still struggling in how to get page source?
All I found is about UIWebview. Something like:
uiwebview = [[UIWebView loadRequest:[NSURLRequest requestWithURL:url]];
Use
NSString *string = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://www.google.com"] encoding:NSUTF8StringEncoding error:nil];
The disadvantage to using +stringWithContentsOfURL: in the main thread is that your UI will block while the request is active. This becomes especially problematic when the user is on a network with high latency, when the server is slow to respond, or if the request ends up timing out. In the last case, the user may see the UI block for a very long time.
The +stringWithContentsOfURL: method also lacks a way to provide you with error information in the event the server does not return a 200 status.
To perform the request asynchronously without blocking the UI, use NSURLConnection and grab the data in the delegate:
- ( void )connection: (NSURLConnection *)connection didReceiveData: (NSData *)data
{
// receivedData is an NSMutableData object
[ receivedData appendData: data ];
}
And then kick off parsing when the connection finishes:
- ( void )connectionDidFinishLoading: (NSURLConnection *)connection
{
[ self parseHTMLData: receivedData ];
}
The URL Loading System Programming Guide will get you started.
Related
I've looked around a lot and cant seem to find a proper answer for my problem. As of now I have a network engine and I delegate into that from each of the view controllers to perform my network activity.
For example, to get user details I have a method like this:
- (void) getUserDetailsWithUserId:(NSString*) userId
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#Details", kServerAddress]]];
request.HTTPMethod = #"POST";
NSString *stringData = [NSString stringWithFormat:#"%#%#", kUserId, userId];
NSData *requestBodyData = [stringData dataUsingEncoding:NSUTF8StringEncoding];
request.HTTPBody = requestBodyData;
NSURLConnection *conn = [[NSURLConnection alloc] init];
[conn setTag:kGetUserInfoConnection];
(void)[conn initWithRequest:request delegate:self];
}
And when I get the data in connectionDidFinishLoading, I receive the data in a NSDictionary and based on the tag I've set for the connection, I transfer the data to the required NSDictionary.
This is working fine. But now I require two requests going from the same view controller. So when I do this, the data is getting mixed up. Say I have a connection for search being implemented, the data from the user details may come in when I do a search. The data is not being assigned to the right NSDictionary based on the switch I'm doing inside connectionDidFinishLoading. I'm using a single delegate for the entire network engine.
I'm new to NSURLConnection, should I setup a queue or something? Please help.
EDIT
Here's the part where I receive data in the connectionDidFinishLoading:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if ([connection.tag integerValue] == kGetUserDetails)
networkDataSource.userData = self.jsonDetails;
if ([connection.tag integerValue] == kSearchConnection)
networkDataSource.searchData = self.jsonDetails;
}
and after this I have a switch case that calls the required delegate for the required view controller.
Anil here you need to identify for which request you got the data,
simplest way to check it is as below,
- (void)connectionDidFinishLoading:(NSURLConnection *)conn
{
// Check URL value for request, url for search and user details will be different, put if condition as per your need.
conn.currentRequest.URL
}
Try using conn.originalRequest.URL it will give original request.
You can do in many ways to accomplish your task as mentioned by others and it will solve your problem . But if you have many more connections , you need to change your approach.
You can cretae a subclass of NSOperation class. Provide all the required data, like url or any other any informmation you want to get back when task get accomplish , by passing a dictionary or data model to that class.
In Nsoperation class ovewrite 'main' method and start connection in that method ie put your all NSURRequest statements in that method. send a call back when download finish along with that info dict.
Points to be keep in mind: Create separte instance of thet operation class for evey download, and call its 'start method'.
It will look something like :
[self setDownloadOperationObj:[[DownloadFileOperation alloc] initWithData:metadataDict]];
[_downloadOperationObj setDelegate:self];
[_downloadOperationObj setSelectorForUpdateComplete:#selector(callBackForDownloadComplete)];
[_downloadOperationObj setQueuePriority:NSOperationQueuePriorityVeryHigh];
[_downloadOperationObj start];
metaDict will contain your user info.
In DownloadFileOperation class you will overwrite 'main' method like :
- (void)main {
// a lengthy operation
#autoreleasepool
{
if(self.isCancelled)
return;
// //You url connection code
}
}
You can add that operation to a NSOperationQueue if you want. You just need to add the operation to NSOperationQueue and it will call its start method.
Declare two NSURLConnection variables in the .h file.
NSURLConnection *conn1;
NSURLConnection *conn2;
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
if(connection == conn1){
}
else if(connection == conn2){
}
}
I wish to fetch data for an array of URLs that return JSON data. I am trying the following code:
for (int i =0; i<numberOfDays; i++)
{
NSData *data = [NSData dataWithContentsOfURL:[wordURLs objectAtIndex:i]];
NSLog(#"%#",[wordURLs objectAtIndex: i]);
[self performSelectorOnMainThread:#selector(fetchedData:) withObject:data waitUntilDone:YES];
}
'wordURLs' are the array of URLs and in my 'fetchedData:' method, I save the returned JSON data to a plist file.
The issue is that for all number of times that the loop runs, the data is returned for only one/two particular URLs (i.e. say for the urls at indices at 1 and 3, or 1 and 2 etc). I log and see that the URLs are different for each time the 'data' variable is initialized.
What is a better way of doing this?
I have used NSJSONSerialization for parsing JSON.
There are much better ways of doing this. The problem with what you are trying to do is that it is synchronous, which means your app will have to wait for this action to be completed before it can do anything else. I definitely would recommend looking into making this into an asynchronous call by simply using NSURLConnection and NSURLRequests, and setting up delegates for them.
They are relatively simple to set up and manage and will make your app run a million times smoother.
I will post some sample code to do this a little later once I get home.
UPDATE
First, your class that is calling these connections will need to be a delegate for the connections in the interface file, so something like this.
ViewController.h
#interface ViewController: UIViewController <NSURLConnectionDelegate, NSURLConnectionDataDelegate> {
NSMutableData *pageData;
NSURLConnection *pageConnection;
}
Then you will need to create/initialize the necessary variables in you implementation
ViewController.m
-(void) viewDidLoad {
pageData = [[NSMutableData alloc] init];
NSURLRequest *pageRequest= [[NSURLRequest alloc] initWithURL:pageURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:4];
pageConnection = [[NSURLConnection alloc] initWithRequest:pageRequestdelegate:self];
}
Then you also need the delegate functions that will get called as the data is retrieved.
-(void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
if (connection == pageConnection) {
[pageData appendData:data];
}
}
-(void) connectionDidFinishLoading:(NSURLConnection *)connection {
if (connection == pageConnection) {
// Do whatever you need to do with the data
}
}
-(void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
if (connection == pageConnection) {
// Do something since the connection failed
}
}
Of course this example only includes one URL being loaded, but you could make it as many as you want. You will of course have to keep track of all of the necessary NSURLConnections so you know where to put the data you received, as well as what actions to take in case of a failure or the connection being completed successfully, but that is not a hard extension from what I have given.
If you see any glaring errors or something does not work, please let me know.
This question already has answers here:
Managing multiple asynchronous NSURLConnection connections
(13 answers)
Closed 8 years ago.
I have two NSURLRequest objects, connecting to a web service and invoking 2 different services.
The problem is that I have a random results, sometimes the first one is displayed first and sometimes the second NSURLRequest is the first.
NSString *urla=#"http://localhost:8080/stmanagement/management/retrieve_dataA/?match_link=";
NSString *uria = [urla stringByAppendingString:self.lien_match];
NSURL *urlla= [ NSURL URLWithString:uria];
NSURLRequest *requesta =[ NSURLRequest requestWithURL:urlla];
NSString *urlb=#"http://localhost:8080/stmanagement/management/retrieve_dataB/?match_link=";
NSString *urib = [urlb stringByAppendingString:self.lien_match];
NSURL *urllb= [ NSURL URLWithString:urib];
NSURLRequest *requestb =[ NSURLRequest requestWithURL:urllb];
connectiona=[NSURLConnection connectionWithRequest:requesta delegate:self];
connectionb=[NSURLConnection connectionWithRequest:requestb delegate:self];
if (connectiona){
webDataa=[[NSMutableData alloc]init];
}
if (connectionb){
webDatab=[[NSMutableData alloc]init];
}
Is that correct what I'm doing? Should I add a small break between the two NSURLRequests?
Because at every view execution I have a random result. (I'm setting the results to two UITableView objects).
I think your "problem" is that self is the connection delegate for both of your connections. These type of connections are asynchronous, so there's no guarantee that A will complete before B. Your code should handle whatever order the web server returns data in.
I suppose you could make the two methods synchronous (don't start B until A completes), but I don't think there's really any need to do that.
The good news is that the NSURLConnectionDelegate callbacks pass you the NSURLConnection object, so you can use that to determine whether you're getting a response to A or B. That information should tell you whether to put the data in the A or B web data object, and whether to update table view A or B, when the request completes. For example:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// determine which request/connection this is for
if (connection == connectiona) {
[webDataa appendData: data];
} else if (connection == connectionb) {
[webDatab appendData: data];
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// determine which request/connection this is for
if (connection == connectiona) {
NSLog(#"Succeeded! Received %d bytes of data",[webDataa length]);
// TODO(?): update the data source for UITableView (A) and call:
[tableViewA reloadData];
} else if (connection == connectionb) {
NSLog(#"Succeeded! Received %d bytes of data",[webDatab length]);
// TODO(?): update the data source for UITableView (B) and call:
[tableViewB reloadData];
}
// release the connection* and webData* objects if not using ARC,
// otherwise probably just set them to nil
}
This solution requires you keeping connectiona and connectionb as persistent ivars, not local variables in the code you posted. It looks like you're probably doing that, but since you don't show their declaration, I just wanted to be sure.
You should also implement the other delegate callbacks, of course, but the above two should give you a good example of the general solution.
I have the worst internet connection atm, so sorry if this has been asked before..
I have an NSURLConnection for getting some json data. Until now it worked perfectly fine to use the delegate method didReceiveData:(NSData*)data to save the received data. I am downloading data from at least seven different pages at the same time. Today, after updati g on of the json-pages to contain more data, the NSData object seemed corrupt. I have recently been told that this delegate does not return the whole data, and thus corrupting my information.
Is there another delegate like the didFinish only it also returns the full complete object? Or do I have to do this myself, like merging two NSData's?
Sorry for stupidity, and grammatical errors are dedicated to iPhone auto-correct.
You must never, ever rely on didReceiveData: returning the full data, because it will break one day. You have to collect your chunks of data in an NSMutableData:
NSMutableData *d = [[NSMutableData alloc] init];
- (void)connection:(NSURLConnection *)c didReceiveData:(NSData *)data
{
[d appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)conn
{
// 'd' now contains the entire data
}
If it's inconvenient for you, you can avoid using NSURLConnection and use a background thread to grab the data in one piece using:
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://web.service/response.json"]];
I have a function which makes GET request to the server. It works fine but for some reason it's called twice. I call function when a button is pressed.
This is function code:
-(void) GETasync: (NSString *) path{
receivedData = [[NSMutableData alloc] init];
NSURLRequest *request=[NSURLRequest requestWithURL:
[NSURL URLWithString: path]
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:60.0];
NSHTTPURLResponse * response;
NSError * error;
NSData * data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSLog(#"data received");
}
So I see in output:
2012-03-07 16:36:41.509 KW2[24136:bf03] data received
2012-03-07 16:36:41.694 KW2[24136:bf03] data received
I also have a function for POST request and it's the same trouble with it.
I am assuming that you are printing out that log within your delegate method connection:didReceiveData:. That method can be called multiple times for one single connection - in fact it is commonly called at least twice.
From the documentation:
The delegate is periodically sent connection:didReceiveData: messages as the data is received. The delegate implementation is responsible for storing the newly received data.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// Append the new data to receivedData.
// receivedData is an instance variable declared elsewhere.
[receivedData appendData:data];
}
EDIT: I see that in your latest edits you have added that logging information (or maybe I did not see it right away - shame on me).
Are you possibly calling that method by a notification handler? If so, have you possibly double-registered for that notification -> hence your handler is invoked twice.
The problem was just in that I wired IBOutlets from File'Owner and First Responder to buttons in IB.
After removing wires from File's Owner, the method started to be called only one time.
Also make sure to check the IBAction connections for your button in Interface Builder. If you copy and paste buttons in IB, you can end up having 2 or more identical IBAction connections for a button, which would cause the method to be executed twice with just one button click.