I recently started to work with NSURLConnection in my project and I'm wondering whether or not the pattern I use to handle the received data is appropriate.
In case I get a 404 or another error, I do not actually want to do anything with the data, so it would be a waste to still append it to my object. Therefore I only want to create the data object once I get a 200 status.
Is it safe to assume that -connection:didReceiveResponse: is called before any of the -connection:didReceiveData: callbacks?
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.data appendData:data];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)response
{
if (response.statusCode == 200) {
self.data = [NSMutableData data];
}
else {
NSLog(#"Connection failed with status code %d", response.statusCode);
[self.connection cancel];
}
}
Yes
didReceiveResponse will call beforeDidReceiveData, and it is possible it get calls many time in one connection as per apple document
You should be prepared for your delegate to receive the
connection:didReceiveResponse: message multiple times for a single
connection; this can happen if the response is in multipart MIME
encoding. Each time the delegate receives the
connection:didReceiveResponse: message, it should reset any progress
indication and discard all previously received data (except in the
case of multipart responses).
Source
Related
I have created a connection using NSURLConnection (Asynchronous web-service call).
i have added all delegates like
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[m_webData setLength: 0];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[m_webData appendData:data];
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(#"ERROR with theConenction");
[connection release];
[m_webData release];
}
i am receiving data ... but i am sending Datable, i do not know how to parse data, how to read data.
my web-service get hit and sends data but how do parse receive datable.
First , as you have received your entire data in m_webData (assuming it is an instance of NSObject) , use this to parse xml using NSXML parser or TBXML parser. Now, real point is that you have to learn how to parse using either of two or any other i don't know (depending on your data received- JSON or XML).
You can learn :
parsing from Xml parsing in iOS tutorial
also TBXML from :http://www.tbxml.co.uk/TBXML/Guides_-_Loading_an_XML_document.html
or http://www.raywenderlich.com/553/xml-tutorial-for-ios-how-to-choose-the-best-xml-parser-for-your-iphone-project
It is better for you to learn first and then use it instead of asking for code.
I need to call a webService (JSON) and pass couple of its contents as parameter to second webService and then call second web service.
Can i use same Mutable Data for this.Like in the
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
where i get the contents, i take the contents in the string and then clear the responseData and call the second webService from there itself.
PS: It should take minimum time to fetch data for both WebService
Is it possible ??
If so, how can i achieve this.
Thank You.
You can use the same variable to hold the response data for two simultaneous services. when you call the second web services again it calls all the NSURLConnection delegate methods,there you have to do like this.
-(void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response {
[self.webData setLength: 0];
NSLog(#"Got Response");
}
-(void) connection:(NSURLConnection *) connection didReceiveData:(NSData *) data
{
[self.webData appendData:data];
NSLog(#"Got Data");
}
-(void) connectionDidFinishLoading:(NSURLConnection *) connection
{
NSLog(#"Received Bytes: %d", [self.webData length]);
// content needs to be segregated here and call the second service through custom delegate/block
}
Here self.webData holds the value until the call the second web service.
didReceiveResponse method initiated once your second service start to receive response where you have to clear your response variable like this '[self.webData setLength: 0]'
The content which you wants to send as parameter to next service needs to segregated from connectionDidFinishLoading method
I'm currently attempting to stream data from Twitter using their streaming API's. I've attached the code below for creating my NSData and appending to it on didReceiveData. For some reason, every time didReceiveData gets a response from Twitter, it's appended on as a new JSON root into the NSData, so when I attempt to parse the NSData into a JSON structure, it blows up.
I couldn't figure out what was going on and posted the JSON into a validator and it noted that there were multiple roots in the JSON. How can I modify the code to continue to append to the existing JSON root? Or is there an easier way to go about deserializing into JSON when there's multiple JSON entries in the NSData?
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
// A response has been received, this is where we initialize the instance var you created
// so that we can append data to it in the didReceiveData method
// Furthermore, this method is called each time there is a redirect so reinitializing it
// also serves to clear it
NSLog(#"Did receive response");
_responseData = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// Append the new data to the instance variable you declared
NSLog(#"Did Receive data");
[_responseData appendData:data];
}
I think what you need is just some extra logic to handle the real-time nature of this. Use your NSMutableData as a container to continue receiving data, but at the end of each batch you should scan the data object for all valid objects, build them, and store them into a different object that holds all the built json objects. In this example lets assume you have this ivar: NSMutableArray *_wholeObjects
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// Append the new data to the instance variable you declared
NSLog(#"Did Receive data");
[_responseData appendData:data];
[self buildWholeObjects]
}
- (void) buildWholeObjects {
NSArray *rootObjects = <#business logic to return one whole JSON object per array element, or return nil if none found#>
if (rootObjects != nil) {
NSUInteger bytesExtracted = 0;
for (rootObject in rootObjects) {
[_wholeObjects addElement:rootObject];
bytesExtracted += rootObject.length;
}
NSData *remainingData = [_responseData subdataWithRange:NSMakeRange(bytesExtracted, _responseData.length - bytesExtracted];
[_responseData setData:remainingData];
}
}
After doing this only access the objects in _wholeObjects, where each element represents a fully valid JSON object that you can deserialize or read in any way you need.
Just for the sake of clarity, lets say the first NSData represents:
{"a":"2"}{"c":"5
When you process it _wholeObjects will have one element representing {"a":"2"}, and _responseData will now be {"c":"5
Then the next stream of data should continue on the object. Lets say the second NSData is:
"}
Now _responseData is {"c":"5"} because we appended the new message onto the remaining old message. We build this one out, and get a second element in _wholeObjects, and _responseData will be empty and ready to receive the next set of data.
Hope that helps some. I think the hard part for you is going to be determining how much of the _responseData is considered a valid JSON object. If they are simple enough you can just count the number of opening {/[ to closing }/] and pull that substring out.
Just to follow up on this topic for anyone dealing with the same thing: I ended up using SBJson which has support for streaming. http://superloopy.io/json-framework/
I'm running a LOT of asynchronous (delegate, not block) NSURLConnections simultaneously, and they all come back very quickly as I'm hitting a LAN server.
Every so often, one NSURLConnection will go defunct and never return.
connection:willSendRequest: is called but connection:didReceiveResponse: (and failure) is not.
Any ideas? I'm wondering if I should make a simple drop-in replacement using CFNetwork instead.
Edit: There's really not much code to show. What I've done is created a wrapper class to download files. I will note that the problem happens less when I run the connection on a separate queue - but still happens.
The general gist of what I'm doing is creating a download request for each cell as a tableview scrolls (in cellForRowAtIndexPath) and then asynchronously loading in an image file to the table cell if the cell is still visible.
_request = [NSMutableURLRequest requestWithURL:_URL];
_request.cachePolicy = NSURLRequestReloadIgnoringCacheData;
_request.timeoutInterval = _timeoutInterval;
if(_lastModifiedDate) {
[_request setValue:[_lastModifiedDate RFC1123String] forHTTPHeaderField:#"If-Modified-Since"];
}
_connection = [[NSURLConnection alloc] initWithRequest:_request
delegate:self
startImmediately:NO];
[_connection start];
As requested, instance variables:
NSMutableURLRequest *_request;
NSURLConnection *_connection;
And delegate methods:
- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response {
NSLog(#"%# send", _URL);
return request;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSLog(#"%# response", _URL);
_response = (id)response;
// create output stream
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
_receivedLength += data.length;
_estimatedProgress = (Float32)_receivedLength / (Float32)_response.expectedContentLength;
[_outputStream write:data.bytes maxLength:data.length];
// notify delegate
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// close output stream
// notify delegate
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(#"%# failure", _URL);
// notify delegate
}
- (void)connection:(NSURLConnection *)connection
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
if(_credential && challenge.previousFailureCount == 0) {
[[challenge sender] useCredential:_credential forAuthenticationChallenge:challenge];
}
}
After poking around in profiler, I found a lead, and it gave me a hunch.
My credentials were failing (not sure why...) and so previousFailureCount was not 0, and hence I wasn't using my credential object.
Changed the code to this and I have no problems:
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
if(_credential) {
[[challenge sender] useCredential:_credential forAuthenticationChallenge:challenge];
}
}
A NSURLConnection will send either didReceiveResponse or didFailWithError.
Often, you're dealing with timeouts before didFailWithError occurs.
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.