In order to use asynchronous http requests in objective c, you need to set a delegate to NSURLConnection. The problem is that I need to make multiple http requests, so having the same delegate (self) wont work.
What is the best way to go about this? Should I make a new delegate class for each http request? Are these delegates just NSObjects?
You have a few options. The two most most common are:
Make a new class for each connection (yes, a subclass of NSObject) and set them as delegates -- have them carry out whatever logic you need when the data is loaded
Set one class as the delegate and store references to all of your NSURLConnections. That way, when your delegate gets - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data called, you can test which NSURLConnection is being used (eg if ([connection == myConnection]) -- or whichever delegate method you're implementing)
What I do is make a class that will handle downloading a file, and notify me when it is done through a selector. I pass it a delegate, a selector and the Info it needs to perform the download.
- (void) downloadFileFrom:(NSString*) httpLocation respondAt:(SEL)selector on:(id)target withParam:(id)param
{
self.finishSelector = selector;
self.delegate = target;
self.responseParams = param;
}
the Class is its own NSURLConnection delegate. Therefore the instance is separated from the others that I may instantiate, And it handles creating its own result for me to work with. I hold onto the param object. which could be anything.
At the end of the download it does a performSelector: on the delegate. passing itself to the delegate.
if ([self.target respondsToSelector:self.selector])
{
[self.target performSelector:self.selector withObject:self.param];
}
then you can create an instance of the downloader and call your method... telling it where to reply to you.
MyDownloader downloader = [[MyDownloader alloc] init];
[downloader downloadFileFrom:#"http://www.mydomain.com/myimage" respondAt:#selector(myFileIsComplete:) on:self withParam: downloader];
[downloader autorelease];
another option is to create a #protocol for your class to respond at, and have you delegate conform to the responder.
That should work, but there is another option to consider. You could make a generic class that creates and calls the NSURLConnection provided they are common enough. Then keep an NSArray or NSDictionary of the classes. One for each connection.
Example: I have an app that needs to download several photos simultaneously. Therefore, I have a GetFlickrPhoto class. It has a custom init method that receives the URL and any other necessary info. Each individual class creates the NSURLConnection and can safely set the delegate to self
This helps keep things contained and very manageable/reusable.
To take it a step further:
The app I mentioned before, also needed to download JSON feeds. So I made a GenericDownload class that took in URL and asynchronously downloaded the NSData and then returned the NSData to the calling delegate via defined success/failure protocols. It didn't care what the NSData contained.
I remodeled GetFlickrPhoto to call GenericDownload and use the returned NSData for a photo. I then made a GetJSON class that also called GenericDownload and parsed the returned NSData into a JSON feed.
Takes a bit more time but in the end you will be glad for maintenance and future projects.
Related
I have a DataManager class that is responsible for fetching data and handing it over to the DatabaseManager, which in-turn will insert it into core data.
The method that exposes itself to the webservice is below
-(void)fetchDetailsForId:(NSString *)userId withFieldInformation:(NSString *)fieldInfo
{
if (SessionIsActive) {
[APIRequester startWithRequest:[NSString stringWithFormat:#"%#?%#",userId, fieldInfo]
completionHandler:^(APIConnectionManager *connection, id user, NSError *error) {
//This is where the results are returned and the API manages its own threads and returns the result lazily
}];
}
}
The above method is within the DataManager class. Now, a few methods in the same class call the above method to get data from the server. This fetched data is then forwarded to the DatabaseManager for inserting into core data. A sample of this is
-(void)fetchCurrentDataForLoggedInUser
{
NSData *fetchedData = [self fetchDetailsForId:loggedInId withFieldInformation:#"all"];
//the fetchedData is then forwarded to DatabaseManager
}
Now, since the web method (first method) gets the data in the background thread (managed by the API), the value of the "fetchedData" in the above method will be null since the web method exits before the API gets the relevant data.
Can someone tell me the most recommended way of handling a situation like this? I am not asking for sample code or anything, just the right direction should be enough. I am looking for a permanent solution than a hack or easy workaround.
Thank you
Make a property for NSData says fetchedData. in .h and synthesize it in .m
then set this property after date fetch then call the method which is required. There are some delegate which tells you data data loading is done so at there you can set your property an from background you can call UI updating method on main thread that gives you your desired result.
you can use selector like this for calling on main thread
[self performSelectorOnMainThread:#selector(rollBar:)
withObject:nil
waitUntilDone:false];
Showing what a novice I am with Objective C here. The second of these two methods is getting called by the method above. Though I have absolutely no idea where? I want to be able to wrap the part that calls the second method in an if statement to determine if the file did exist based on the returned Boolean. Example code would be appreciated, if anyone could also explain how this second method gets called that would also be fantastic.
-(void) queryResponseForURL:(NSURL *)inURL {
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:inURL];
[request setHTTPMethod:#"HEAD"];
NSURLConnection * connection = [NSURLConnection connectionWithRequest:request delegate:self];
// connection starts automatically
}
-(BOOL)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
if([(NSHTTPURLResponse *)response statusCode] == 200){
NSLog(#"file exists");
return YES;
}else return NO;
}
It is getting called by the NSURLConnection object, so you can't intervene in the code that calls it. But you shouldn't need to - the purpose of a delegate method is to enable you to hook in to that inaccessible code.
The method signature is this, by the way:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
It doesn't have a return value and you can't change the method signature, as it's part of the NSURLConnection object's protocol. Remember, you don't call this method, the connection object does. But you can respond to the method. Instead of attempting to return a BOOL, you can deal with your 'if file..' code inside this method, or call another method from it. You should bear in mind that this is triggered (asynchronously) when a response is confirmed to have been initiated - so you can check HTTP header info such as mime type - but it doesn't guarantee a completed data transfer (there are other delegate methods for that).
See also the developer guide to NSURLConnection
The second method is a delegate or callback method. The callback method is called when your connection instance receives a response from the server your connecting with. NSURLConnection has a few informal protocols on NSObject. This means that methods defined in these protocols will automatically be called when any subclass of NSObject has implemented these methods. Your subclass of NSObject has implemented one of these callback methods, therefore this method is called. Most delegates are declared as formal protocols and need to be explicitly defined in your classes in order to make use of them. The informal protocols of NSURLConnection are exception to the rule, though this is about to change in future versions of iOS.
P.S.: a protocol in Objective-C is essentially the same as an interface in Java / C#.
More info here: http://developer.apple.com/library/ios/ipad/#documentation/Foundation/Reference/NSURLConnectionDataDelegate_protocol/Reference/Reference.html
I guess you want to cancel the connection when you receive the wrong response? What you should do is the following:
Store your URLConnection in an ivar or property (personally I'd prefer a property).
In your -connection:didReceiveResponse delegate method, if the response status code isn't an appropriate value, cancel your connection. You can use your ivar or property for this purpose.
I have a class, which has a delegate based system for sending different type of requests. it uses delegate to tell the object when the request is complete and also if it was a success o an error.
Now, I also have to check what type of request was it in response to take appropriate action.
I have wrapper class that should give me a block based interface for the same.
I pass a completion-block and an error-block to a request method which should internally use this delegate based class.
And when the response comes, should automatically call the appropriate handler for that request type and depending on success and error as well.
I saw a similar question on SO, but it was a little unclear to me, So please give a general idea of how to go about it instead of marking it as duplicate straight away.
Here is one way to do it. Use this RAExpendable class to dynamically build a delegate with a block based implementation.
Let's say your delegate is:
#protocol XDelegate
-(void) foo:(id)response;
#end
Add RAExpendable.h, RAExpendable.m from https://github.com/evadne/RAExpendable to your project. Dynamically add the delegate method:
RAExpendable *expendable = [RAExpendable new];
[expendable addMethodForSelector:#selector(foo:) types:"v#:#" block:^(id x, SEL sel, id response){
NSLog(#"response is %#", response);
}];
And set the expendable class as your delegate:
someObject.delegate = expendable;
Now, if you do this:
[expendable performSelector:#selector(foo:) withObject:#"OK"];
You get the string response is OK. Replace NSLog with whatever success/failure implementation you see fit. From now on, when you call foo:, the block executes instead.
If you want to modify this for your use case, note that the parameters for this example were v#:#, which according to the Type Encoding guide of the runtime means: void return, self, SEL, object. self and SEL are the two hidden parameters present on every Objective-C methods, the third parameter is the first non hidden parameter of the method. The signature of the block has to match the signature of the method.
With REKit, you can make a delegate dynamically like below:
id dynamicDelegate;
dynamicDelegate = [[NSObject alloc] init];
[dynamicDelegate respondsToSelector:#selector(foo:) withKey:nil usingBlock:^(id receiver, id response) {
NSLog(#"response is %#", response);
}];
someObject.delegate = dynamicDelegate;
I have a class named "ServerDataLayer" that holds a NSURLConnection, and a NSMutableData that its writing the received HTTP data in to. When the connection finishes, it simply fires a delegate that my caller passed itself as a reference, the method looks like this:
-(void) serverDataLayerResponse:(id)entity
{
if ([entity isMemberOfClass:[LoginResponse class]])
{
LoginResponse *response = (LoginResponse*)entity;
NSLog(#"Error Code: %d", response.errorCode);
NSLog(#"Error Message: %#", response.errorMessage);
NSLog(#"Registered: %c", response.registered);
NSLog(#"AuthToken: %#", response.authToken);
[AppData shared].authToken = response.authToken;
ServerDataLayer *request = [[[ServerDataLayer alloc] initWithServer:_serverUrl delegate:self] autorelease];
[request getPlayerDetails];
//[_server getPlayerDetails];
}
}
Here's my problem...the internal _receivedData and _connection variables are currently in use whilst this delegate method is in progress. I wanted to use my same ServerDataLayer instance to fire another request off "[_server getPlayerDetails]", but the _connection and _receivedData variables internally were getting overwritten and I was getting in to a mess about when to retain/release at the right time.
So my work around was just to instantiate the ServerDataLayer each time I wanted to talk to the server. Now...in the example above, I'm instantiating the request with an 'alloc', and setting an 'autorelease' as I lose scope of this 2nd request. Will this 2nd request stay in memory whilst it's NSURLConnection is busy internally performing the request?
I'm getting a bit lost at this point on how to manage the object references for this kind of process. Any help would be appreciated.
An NSURLConnection, if used via the delegate methods will attach itself as an input to a run loop. However it won't retain its delegate. So your ServerDataLayer would be deallocated (and hopefully remember to cancel the connection). You could use object associations to give your object the same lifecycle as the URL connection, if you were suitably careful about the potential retain loop.
If you use sendAsynchronousRequest:... then you'll probably be fine anyway; assuming you reference self or any instance variable in the completion block then you'll be retained by the block and live for at least as long as it does.
There's really no need to confuse yourself over retain/release any more. Even if ARC isn't an option, you can just declare the relevant instance variables as retain properties within a class extension and use self.property notation to set new values. Retains and releases will be handled for you.
The only caveat is that you should never use dot notation in either your init or dealloc as a special case of the rule that it isn't safe to call methods on a class that's only half instantiated or is half destroyed.
Just use multiple ServerDataLayer instances.
This blog offers a nice solution for handling multiple NSURLConnections: make a custom "CustomURLConnection" class that has an additional tag property.
http://blog.emmerinc.be/index.php/2009/03/02/custom-nsurlconnection-class-with-tag/
http://blog.emmerinc.be/index.php/2009/03/15/multiple-async-nsurlconnections-example/
Basically, he has simply added a tag property to the exsisting NSURLConnection:
CustomURLConnection.m
- (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:(BOOL)startImmediately tag:(NSString*)tag {
self = [super initWithRequest:request delegate:delegate startImmediately:startImmediately];
if (self) {
self.tag = tag;
}
return self;
}
then, later in the normal NSURLConnection loading methods, you can do:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
//Log the connection’s tag
CustomURLConnection *ttttag = (CustomURLConnection *)connection; // **HERE**
NSLog(#”%#”, ttttag.tag);
NSMutableData *dataForConnection = [self dataForConnection:(CustomURLConnection*)connection];
[connection release];
}
So, that's where I'm having trouble. The way I see it, this is how things go:
I create a "connection+tag"
The first code snippet I posted above creates a regular "connection" (no tag), which will eventually call the the normal NSURLConnection methods like connectionDidFinishLoading. What happens to the tag at this point?
In the connectionDidFinishLoading method I'm able to cast the connection back into a "connection+tag", then find that missing tag information that had been discarded. How?
Maybe I'm just confusing myself, but it seems as if the tag was discarded when it starts down the normal NSURLConnection path. But then by casting it as the subclass, I'm again able to recover the tag property. Where did it live/go in the mean time?
Could someone with a better understanding of inheritance explain this to me?
With this code:
[[CustomURLConnection alloc] initWithRequest:... delegate:... startImmediately:... startImmediately tag:...];
you create an instance of CustomURLConnection. Now here is where your understanding is wrong: this CustomURLConnection object can freely call all methods of its superclasses but it will always remain a CustomURLConnection. The tag is always there.
The methods that are defined in the superclass such as initWithRequest:delegate:startImmediately: don't know about the tag but they don't have to, either. When the delegate method gets called:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
the connection argument is the very same CustomURLConnection that you created yourself above. The type in the method signature is different but that doesn't matter; because you know that this connection is of the CustomURLConnection type, you can just cast the connection object to the correct type and access the new property. But even if you wouldn't do that, the tag would still be there all the time.
I'm not sure what you mean by:
The first code snippet I posted above creates a regular "connection" (no tag).
What you've done here is create a subclass of NSURLConnection. Anywhere you can use the latter, you can use the former. NSURLConnection* means "a pointer to an NSURLConnection* or a subclass of it." So the original object you created was a CustomURLConnection and it included an extra ivar. That ivar doesn't disappear just because intermediary users refer to it by its superclass.