I am currently downloading and caching several different data sets from my server. However I am swapping all of my code from ASI over to NSURLConneciton stuff. and I am at the point where I am ready to pass my caches over to my parser delegates however they accept a NSData type as a parameter
- (void)startTheParsingProcess:(NSData *)parserData
where my cache is of type
NSCachedURLResponse
as you can see here
-(NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
if (dataSetToParse == #"a"){
NSCachedURLResponse *aCachedResponse = cachedResponse;
aCachedResponse = nil;
NSDictionary *newUserInfo;
newUserInfo = [NSDictionary dictionaryWithObject:[NSDate date] forKey:#"Cached Date"];
aCachedResponse = [[NSCachedURLResponse alloc] initWithResponse:[cachedResponse response] data:[cachedResponse data] userInfo:newUserInfo storagePolicy:[cachedResponse storagePolicy]];
NSLog(#"%#", aCachedResponse);
return aCachedResponse;
}
if (dataSetToParse == #"b"){
NSCachedURLResponse *bCachedResponse = cachedResponse;
bCachedResponse = nil;
NSDictionary *newUserInfo;
newUserInfo = [NSDictionary dictionaryWithObject:[NSDate date] forKey:#"Cached Date"];
bCachedResponse = [[NSCachedURLResponse alloc] initWithResponse:[cachedResponse response] data:[cachedResponse data] userInfo:newUserInfo storagePolicy:[cachedResponse storagePolicy]];
NSLog(#"%#", bCachedResponse);
return bCachedResponse;
}
return nil;
}
any example code would be helpfull thanks or just pointing me in the right direction :)
I am looking at the NSCachedURLResponse definition in the apple docs, and it says that NSCachedURLResponse has the data that I want I'm just not sure how I can get to it... to use with my parser delegates.
You mean something like that?
NSCachedURLResponse *cachedResponse = nil; // Put your code to get real response
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)[cachedResponse response];
NSData *responseData = [httpResponse data];
NSCachedURLResponse has also a direct method data, but I cannot test if it's the same result as the code above.
Cache suggestion
I see that you have your own methods that deal with caching. I would suggest subclassing the NSURLCache, if you need to modify caching and, say, add an offline mode. If you subclass it and implement some methods, like (but not limited to):
– cachedResponseForRequest:
– storeCachedResponse:forRequest:
life may become easier for you. You would just set global app cache in application delegate through [NSURLCache setSharedURLCache:[CustomURLCache new]]; call.
Then all your standard NSURL-whatever classes would behave as if they work with system cache, and you normally won't have to deal with direct calls to get or store the cache, the system would call them automatically.
Related
I am loading a plist via NSURLConnection into NSMutableData.
After that is done I want to read the PLIST into a NSMutableDictionary.
And then add the objects into my array to display them in a tableview.
But at the moment I don't know how to extract the data from NSMutableData into my NSMutableDictionary.
If I save the data local as plist on the iPhone in some folder and then read the plist into my Dictionary it works. But isn't there a way to do this directly?
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
receivedData = [[NSMutableData alloc] initWithData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSData *data = [[NSMutableData alloc] initWithData:receivedData];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
NSDictionary *myDictionary = [unarchiver decodeObjectForKey:#"Beverage"];
[unarchiver finishDecoding];
beverageArray = [[NSMutableArray alloc] init];
beverageArray = [myDictionary objectForKey:#"Beverage"];
NSLog(#"%#", beverageArray);
}
Before using NSURLConnection I used this which works:
- (void) makeDataBeverage {
beverageArray = [[NSMutableArray alloc] init];
NSMutableDictionary *beverageDic = [[NSMutableDictionary alloc]initWithContentsOfURL:[NSURL URLWithString:NSLocalizedString(#"beverage", nil)]];
beverageArray = [beverageDic objectForKey:#"Beverage"];
Now I want to the same with using NSURLConnection.
Assuming you have the complete data(*), you'll want to look into the NSPropertyListSerialization class. Its +propertyListWithData:options:format:error: method should get you what you're looking for, and you can use the options parameter to get the results as a mutable dictionary or array.
(*)It sounds like you have the complete data, since you say you can write it to a file and then read it in using dictionaryWithContentsOfFile: or similar, but it doesn't look like you're guaranteed to get it from the code you've shown. You're creating a new data in -connection:didReceiveData:, but that delegate method can be called multiple times as the data arrives in pieces. (I'm guessing it just happened to arrive all in one piece for your testing... this may not always be true, especially on a mobile device.) Instead, you probably want to create an empty mutable data when you start your NSURLConnection (or in -connection:didReceiveResponse:), append to it in -connection:didReceiveData:, and parse it in -connectiondidFinishLoading:. Or even better, since the property list parser can't do anything with a partial data anyway, use the new +[NSURLConnection sendAsynchronousRequest:queue:completionHandler:] if you're targeting iOS 5.0+.
First of all the questions are failry simiple.. if you just want to see what they are skip to the bottom of this post and you will see them in bold.. for more detail then you can read the rest of this post...
I am just trying to iron out my NSURLConnection so that its working smoothly and I understand this properly. There is a profound lack of example/tutorials for Asynchronous connections on the internet or not any that I can find that explaine what is going on with any level of depth other than getting the connection up and running which after working on it seems pretty simple. Hopefully this question can full the void that I feel is out there for other users.
So, in my .h file i have imported the foundations headers and declared the methods required for the received or lack of received data (errors etc).
.h
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h> //add foundations
//.. other headers can be imported here
#interface MyViewController: UITableViewController {
//Im not setting any delegates to access the methods because Is all happening in the same
//place so I just use the key word 'self' when accessing the methods declared below
//I'm not sure if this is the best thing to do but I wasn't able to get my head around declaring the delegate or how it would help me with the way I have set up my request etc.
}
- (IBAction)setRequestString:(NSString *)string; //this method sets the request and connection methods
//these methods receive the response from my async nsurlconnection
- (void)receivedData:(NSData *)data;
- (void)emptyReply;
- (void)timedOut;
- (void)downloadError:(NSError *)error;
So thats my header file.. pretty simple not much explaining needed.
.m
//call setRequestString from some other method attached to a button click or something
[self setRequestString:#"rss.xml"];
//..
- (IBAction)setRequestString:(NSString *)string
{
//Set database address
NSMutableString *databaseURL = [[NSMutableString alloc] initWithString:#"http:www.comicbookresources/feeds/"]; // address not real jsut example
//append the string coming in to the end of the databaseURL
[databaseURL appendString:string];
//prepare NSURL with newly created string
NSURL *url = [NSURL URLWithString:databaseURL];
//AsynchronousRequest to grab the data
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if ([data length] > 0 && error == nil){
[self receivedData:data];
}else if ([data length] == 0 && error == nil){
[self emptyReply];
}else if (error != nil && error.code == NSURLErrorTimedOut){ //used this NSURLErrorTimedOut from foundation error responses
[self timedOut];
}else if (error != nil){
[self downloadError:error];
}
}];
}
now set up the methods that were initialized in the .h file and called in the if statement above
- (void)receivedData:(NSData *)data
{
NSString* newStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"%#", newStr); //logs recived data
//now you can see what data is coming in on your log
//do what you want with your data here (i.e. start parsing methods
}
- (void)emptyReply
{
//not sure what to do here yet?
}
- (void)timedOut
{
//also not sure what to do here yet?
}
- (void)downloadError:(NSError *)error
{
NSLog(#"%#", error);
UIAlertView *errorAlert = [[UIAlertView alloc] initWithTitle:#"Error!" message:#"A connection failure occurred." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[errorAlert show];
}
Cool so that pretty much the basics of what I have done right there.. now the questions I have are as follows.
Question one:
Where I call NSURLConnection like so
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
What is happening here what is the ^ for is that executing that whole block (including the if statements) on a different thread or something? because it looks alot like grand central dispatch formatting but slightly different.
Question two:
what should I be doing inside emptyReply & timedOut methods?
Question three:
How would I incorporate caching into this? I would like to cache the responses I get back from different requests. i.e. with my setRequestString you will see there is a string input parameter, so i can request different rss feeds with the same method.. I need to figure out how to cache these responses into individual caches.. but im not sure where to start with it.
Finally
If you have made it this far, thank you very much for reading my question. Hopefully with your responses we can get a pretty nice solution going here.. that other people can use for themselves and pick and choose the bits and peices they need that works for there own solution..
Anyway thank you very much for reading and I look forward to your replies.. even if they are just refrences to tutorials or examples you think might help me.. anything is good I just want to fully understand whats going on and whats a good solution.
Read about blocks in Apple documentation. Its new. Or you can read here
You can show errors such as request timed out etc. You don't really have to handle them separately than the error one unless you have special logic.
Try this for caching
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:TIMEOUT_INTERVAL];
This might be a dumb question. Sorry if it is.
But Im working on a project that consumes web services. I can connect to the web service and get the data I need fine.
I would like to have a method that returns this data obtained from the web service to the caller. The only problem is that the data is only obtained inside the ConnectionDidFinishLoading method, and I can't access this data from my method.
here is my code, that works fine:
- (NSData *) dataForMethod:(NSString *)webMethod withPostString:(NSString *)postString
{
NSURL *url = [NSURL URLWithString:[SigameWebServiceAddress stringByAppendingFormat:#"%#%#", #"/", webMethod]];
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
NSString *msgLength = [NSString stringWithFormat:#"%d", [postString length]];
[req addValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[req addValue:msgLength forHTTPHeaderField:#"Content-Length"];
[req setHTTPMethod:#"POST"];
[req setHTTPBody: [postString dataUsingEncoding:NSUTF8StringEncoding]];
conn = [[NSURLConnection alloc] initWithRequest:req delegate:self];
if (conn)
{
webData = [NSMutableData data];
}
// I WOULD LIKE TO RETURN WEBDATA TO THE CALLER HERE, BUT WEBDATA IS EMPTY NOW, THE
//connectionDidFinishLoading ONLY GETS CALLED WITH THE DATA I WANT AFTER THE COMPILER
//IS DONE EXECUTING MY METHOD.
}
-(void) connection:(NSURLConnection *) connection didReceiveResponse:(NSURLResponse *) response
{
[webData setLength: 0];
}
-(void) connection:(NSURLConnection *) connection didReceiveData:(NSData *) data
{
[webData appendData:data];
}
-(void) connection:(NSURLConnection *) connection didFailWithError:(NSError *) error
{
NSLog(#"FATAL ERROR");
}
-(void) connectionDidFinishLoading:(NSURLConnection *) connection
{
NSLog(#"DONE. Received Bytes: %d", [webData length]);
NSString *theXML = [[NSString alloc] initWithBytes: [webData mutableBytes] length:[webData length] encoding:NSUTF8StringEncoding];
//---shows the XML---
NSLog(#"%#", theXML); //NOW, THIS IS THE DATA I WANT. BUT HOW CAN I RETURN THIS TO
//THE CALLER. I MEAN, THE CALLER THAT CALLED MY METHOD
//+ (NSData *) dataForMethod: withPostString:
}
Any help here is appreciated!
Thanks
There are really two ways to go about this.
Create a delegate interface
Use Blocks
I would strongly advise against using the synchronous methods - unless you are/have created your own asynchronous framework around them (i.e. you are manually starting another thread and executing your synchronous request on that thread). In the long run you will realize you need the requests to be async, and you'll have to re-work everything such that they are.
To give a quick overview of the two options I gave:
1. Create a delegate interface
The idea here is to create a class which performs the request, and create a protocol the caller must implement. When the request is complete, you will invoke a specified method on the delegate with the data:
The protocol might look something like this:
#protocol RequestClassDelegate <NSObject>
- (void)requestCompleted:(ResponseClass *)data;
- (void)requestError:(NSError *)error;
#end
The class which makes the request might look something like this:
#interface RequestClass : NSObject
- (void)makeRequest:(id<RequestClassDelegate>)delegate;
#end
And the request class implementation might contain some of the following, in addition to your connection logic:
#implementation RequestClass
{
__weak id<RequestClassDelegate> _delegate;
}
// Connection Logic, etc.
- (void)makeRequest:(id<RequestClassDelegate>)delegate
{
_delegate = delegate;
// Initiate the request...
}
-(void) connectionDidFinishLoading:(NSURLConnection *) connection
{
NSString *theXML = [[NSString alloc] initWithBytes: [webData mutableBytes] length:[webData length] encoding:NSUTF8StringEncoding];
// Processing, etc.
// Here we'll call the delegate with the result:
[_delegate requestCompleted:theResult];
}
#end
2. Use Blocks
This solution is much the same as the first solution - but, a bit more elegant in my opinion. Here, we'll change the RequestClass to use blocks instead of a delegate:
typedef void (^requestCompletedBlock)(id);
typedef void (^requestErrorBlock)(NSError *);
#interface RequestClass : NSObject
#property (nonatomic, copy) requestCompletedBlock completed;
#property (nonatomic, copy) requestErrorBlock errored;
- (void)makeRequest:(requestCompletedBlock)completed error:(requestErrorBlock)error;
#end
And the implementation of that might look something like this:
#implementation RequestClass
#synthesize completed = _completed;
#synthesize errored = _errored;
// Connection Logic, etc.
- (void)makeRequest:(requestCompletedBlock)completed error:(requestErrorBlock)error
{
self.completed = completed;
self.errored = error;
// Initiate the request...
}
-(void) connectionDidFinishLoading:(NSURLConnection *) connection
{
NSString *theXML = [[NSString alloc] initWithBytes: [webData mutableBytes] length:[webData length] encoding:NSUTF8StringEncoding];
// Processing, etc.
// Here we'll call the delegate with the result:
self.completed(theResult);
}
#end
It sounds like you are trying to use return the data synchronously from your method, but you are using an asynchronous method (using an NSURLConnection and presumably calling its start method) to begin retrieving data. If you really want your method to return its result synchronously, read on. As #Steve says in another answer, however, you may also reconsider your interface design and instead implement it using an asynchronous approach and use his recommendations for either a delegate or block-based interface.
If you want to return the data synchronously from your method, use a synchronous request. So change this part of your code:
conn = [[NSURLConnection alloc] initWithRequest:req delegate:self];
[conn start]; // I presume you have this somewhere
if (conn)
{
webData = [NSMutableData data];
}
with something more like this:
NSURLResponse *response = nil;
NSError *error = nil;
webdata = [NSURLConnection sendSynchronousRequest:req returningResponse:&response error:&error];
if (webdata) {
return webdata;
}
else {
// Handle error by looking at response and/or error values
return nil;
}
You will no longer need any of your delegate code if you use this approach. You will be limited in some ways though. For example, if your web service requires authentication via something other than URL parameters you can't use this approach.
Steve's answer is great and I can only suggest the way using blocks. Actually, as I am new into Objective-C I implemented the approach steve outlined. It works perfectly.
The Post for more details and my own point of view you can find here:
http://kerkermeister.net/how-to-build-an-cocos2d-ios-app-communicating-with-a-restful-api-the-sequence/
The Post contains all the tiny steps you need to follow to get Steve's solution approach with blocks working. That includes:
- an updateable view that will render information as soon as retrieved from Web API asynchronously
- a controller invoking the HTTP request to the Web API
- the actual HttpRequest class that uses iOS standard NSURLConnections
- a model class that uses blocks as callbacks to update its data
Your going to have to either implement a separate method in which you use the data once the data has been returned by the connectionDidFinishLoading method or make the request synchronously. The reason I believe the above does not work is because the request is happening on a separate thread, so the main thread continues, but does not actually have the data.
This is a good way to do that if synchronous is what you want:
Does all NSURLConnections connect asynchronously? iOs
In order to download data from webserivce - use NSURLSession -
A URL session task that returns downloaded data directly to the app in memory.
// 1. create NSURL link to your webservice
NSString *dataUrl = #"DATA_LINK_TO_WEBSERVICE";
NSURL *url = [NSURL URLWithString:dataUrl];
// 2. create a NSURLSessionDataTask
NSURLSessionDataTask *downloadTask = [[NSURLSession sharedSession]
dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
//Handle response here
}];
// 3.resume the task
[downloadTask resume];
Refernces:
apple documentation refrence:
https://developer.apple.com/documentation/foundation/nsurlsessiondatatask?language=objc
Raywanderlich great cookbook:
https://www.raywenderlich.com/2392-cookbook-using-nsurlsession
Your going to need to parse the XML that comes back. There are some good Objective C XML parsers out there. One in particular is made for ease of use....
http://nfarina.com/post/2843708636/a-lightweight-xml-parser-for-ios
It's a very light weight parser for extracting the values you want from XML. I've used many times with great success and little hassle. Here is how I query a web address and turn it into data.
NSString *query = [NSString stringWithFormat:#"http://WEB_ADDRESS_FOR_XML];
NSURL *URL = [NSURL URLWithString:query];
NSData *data = [NSData dataWithContentsOfURL:URL];
Or with NSURLConnection, in the did receive data:
-(void) connection:(NSURLConnection *) connection didReceiveData:(NSData *) data
{
//USE THE DATA RETURNED HERE....
}
Then use the Parser from my link to get the contents:
SMXMLDocument *document = [SMXMLDocument documentWithData:data error:NULL];
NSLog("\nXML Returned:%#",document);
I have a program that has multiple url request so I used the the code in http://snippets.aktagon.com/snippets/350-How-to-make-asynchronous-HTTP-requests-with-NSURLConnection and put it in it's own class (class B).To call the class I am simple initializing class B in class A, sending a url to class B's get method([classname get:url]) and then getting the server response upon return.
The problem is that I am getting defeated by race conditions due to the fact that the didReceiveData: method is not complete by the time my method is returned.
I have gone through the developer example of using NSUrlConnection and they are updating views once the response finally came in so they didn't have to fight this problem.
Thank you so much for your help.
I need to keep the calls asynchronous due to the number of them I have to make but I am open to any suggestions.
Edit (moved from answer)
I changed the code to GCD based off of a tutorial and I am still getting defeated by the race condition. Here is the code that I am using now:
I changed it to GCS based on on your suggestion but I am still getting caught by the race condition. Below is the code that I changed it to and I am calling it by:
NSString *responseStringClassA = [InitalizedInstanceOfClassA LogIn:#"username" #"password"];
//Log into the server
-(NSString *)logIn: (NSString *) username password:(NSString *) password
{
NSString* returnString;
dispatch_queue_t downloadQueue = dispatch_queue_create("Login", NULL);
dispatch_async(downloadQueue, ^{
BOOL success = YES;
NSString *urlAsString =[NSString stringWithFormat:#""URL HERE];
NSLog(#"url sent out: %#", urlAsString);
NSURL *url = [NSURL URLWithString:urlAsString];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
NSError *error = nil;
NSData *connectionData = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:nil error:&error];
NSLog(#"Connection Data: %#", [[NSString alloc] initWithData:connectionData encoding:NSASCIIStringEncoding]);
[returnString isEqualToString:[NSString stringWithUTF8String:[connectionData bytes]]];
if ([connectionData length] > 0 && error == nil) {
//success
success = YES;
}
else if([connectionData length] == 0 && error == nil){
//nodata
success = YES;
}
else if(error != nil){
//error ..
success = NO;
}
dispatch_async(dispatch_get_main_queue(), ^{
[returnString isEqualToString:[[NSString alloc] initWithData:connectionData encoding:NSASCIIStringEncoding] ];
});
});
return returnString;
}
It's the very purpose of asynchronous requests that the intial method returns almost immediately without having done the work. Later, when the work has been done, you will be notified and you can access and use the result.
But obviously, you're looking for something else than asynchronous operations. And alternative would be to use synchronous URL requests but run them from separate threads. The best way to achieve this is to use GCD (grand central dispatch).
Note that you may not update the user interface from background threads. Instead, when the URL request has finished and you want to display your results, you have to call performSelectorOnMainThread (part of NSObject) for that.
Currently, I have a class that is parsing XML and sending the dictionary that the XML is parsed to to a view controller.
Here is a snippet of the code that sends the dictionary to the other class (where "response" is the dictionary):
if ([elementName isEqualToString:#"SessionData"])
{
// We reached the end of the XML document
// dumps dictionary into log
NSLog(#"Dump:%#", [response description]);
// sends dictionary to the VC
CardSetupViewController *setup = [[CardSetupViewController alloc]init];
setup.response = self.response;
//checks
NSLog(#"%# lololololol", [setup.response description]); //THIS WORKS FINE!!
return;
}
At that point, the code works fine. That NSLog marked with //THIS WORKS FINE!! works... obviously. Here is the method in the ViewController:
- (BOOL)authorize //this
{
AddCard *addCard = [[AddCard alloc]init];
ServerConnection *connection = [[ServerConnection alloc]init];
//XMLParser *xmlParser = [[XMLParser alloc]initXMLParser];
//serverReturn posts the data and is the ACTUAL server response in NSData form
NSData *serverReturn = [connection postData:[addCard textBoxToXml:
[self nameOnCardGet]:
[self ccNumGet]:
[self expMoGet]:
[self expYrGet]:
[self cvvGet]:
[self zipGet]:
[self nickNameGet]:
[self pinGet]]];
//This takes the information from the server and parses it to "response"
//Creates and inits NSXMLParser Object
NSXMLParser *nsXmlparser = [[NSXMLParser alloc] initWithData:serverReturn];
//Create and init our delegate
XMLParser *parser = [[XMLParser alloc] initXMLParser];
//set delegate
[nsXmlparser setDelegate:(id <NSXMLParserDelegate>) parser];
//initiates self.response THIS MAY NOT BE NEEDED
//response = [[NSMutableDictionary alloc]init];
//parsing
BOOL success = [nsXmlparser parse];
//error catch testing
if (success) {
NSLog(#"No errors");
}
else {
NSLog(#"Error parsing document!");
}
//dump
NSLog(#"ZOMG CHECK DIS OUT%#", [response description]);
return NO;
}
Basically, the NSLog that states "ZOMG CHECK DIS OUT" is returning (null) and I can't figure out why. No compilation errors, it is a property/synthesize as well. Any ideas?
Thanks in advance. Oh, and please excuse my NSLog comments. I had to differentiate from different parts of the code, and I was in a good mood.
Edit: I am using Automatic Reference Counting. Don't worry, nothing is leaking.
In your first code block, you generate a CardSetupViewController and then leak it. It is unrelated to whatever object is running the second code block. I assume that your second view controller is from your NIB?
Note that you're also leaking your NSXMLParser.
Your [response description], whatever that is, is probably an autoreleased object that gets released before ZOMG CHECK DIS OUT. Retain it and see if that works. Don't forget to release it when you're done with it.