I'm trying to implement an upload queue in my application. I'm putting my RKRequests into RKRequestQueue and call [queue start]. But, as we all know, network connection is something that is not lasts forever. I'm using RKReachabilityObserver now to determine when to suspend and resume my queue, and it is working fine (at least now, however I've heard about some issues with reachability code in RestKit). This lets me to stop sending new data until network is available again. But when network connection is lost, all active RKRequests are issuing - (void)request:(RKRequest *)request didFailLoadWithError:(NSError *)error where, I thought, I will be able to put my RKRequest back in queue again.
So, I tried this:
- (void)request:(RKRequest *)request didFailLoadWithError:(NSError *)error
{
NSLog(#"Request failed");
[[request queue] cancelRequest:request];
[[request queue] addRequest:request];
}
but I'm getting an EXC_BAD_ACCESS somewhere in didFailLoadWithError method of RKRequest.
My question is: how can I requeue a RKRequest?
Instead of cancelling and adding to queue, do:
[request send];
But the best solution for this would really be to use RKClient, it makes things easier. You would not have to worry about queue. The client comes with and instance of RKRequestQueue and does all the magic behind the scenes, specifically it adds all requests configured for the given client to the clients request queue and dispatches them for you.
Related
I am trying to start a second NSURLConnection after starting the first one. My first one works perfectly: the appropriate delegates are all called, and everything executes as planned. However, after the first one finishes, I create a second one, and nothing happens (the delegate is never called). What could be happening? I know I can't reuse the same NSURLConnection, but I reinitialise it before using it again, so it should be a completely new connection.
Here's my code for starting (both) connections. It's the same instance variable, but it's reinitialised. Also note that the second one is not started until the first one has finished running completely.
if (connection) {
[connection cancel];
}
currentResponse = nil;
error = nil;
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
if (!connection) {
NSLog(#"Connection could not be initialized.");
[self connectionFinished];
} else {
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[connection start];
}
You haven't shared how you're running it "on a different thread", but the typical problem is if you are using dispatch queues or operation queues, your connection is running asynchronously, itself, and therefore the dispatched operation is completing and getting released and you're losing your connection.
A couple of possible solutions:
You can perform your network operations synchronously in this background operation of yours (this is the only time you should do synchronous network operations). This is the simplest solution, though you haven't explained what you're doing with your NSURLConnection, so this technique may or may not work for you. But if you're just trying to download something from a URL, you can do:
NSData *data = [NSData dataWithContentsOfURL:url
options:0
error:&error];
This approach doesn't work if you're doing any a little more complicated that requires the NSURLConnectionDataDelegate methods, such challenge-response authentication or if you are streaming using didReceiveData to reduce your app's memory footprint or for performances reasons, etc. But if you're just trying to download data from a remote server (e.g. retrieving an image, an XML/JSON feed, etc.), this is easiest.
In a similar vein (i.e. you don't need the NSURLConnectionDataDelegate methods), but you're you're creating some rich NSURLRequest for your connection, then you can use either sendAsynchronousRequest or sendSynchronousRequest.
If you need the NSURLConnectionDataDelegate calls, you can use the setDelegateQueue (designating a NSOperationQueue) or scheduleInRunLoop (designating a NSRunLoop), and it will automatically dispatch the connection updates to the appropriate queue/runloop. Just make sure to initWithRequest using the startImmediately option of NO, set the delegate queue or runloop, and then start the connection. With this technique, you preserve the full richness of NSURLConnectionDataDelegate if you absolutely need it.
Alternatively, if you're not using operation queues, you could also keep your background operation alive until the connection is done. This approach gives you synchronous behavior in you background operation (keeping your connection alive), while preserving the NSURLConnectionDataDelegate methods. This technique is demonstrated by Apple's XMLPerformance Sample (see the downloadAndParse method of CocoaXMLParser.m and LibXMLParser.m), where they initiate the NSURLConnection and then use the following construct to keep the background operation alive until the NSURLConnectionDataDelegate methods end up setting the done instance variable:
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
} while (!done);
I confess that I find this final approach vaguely dissatisfying and would lean towards the other alternatives, depending upon what flexibility and functionality you need from your NSURLConnection. For us to provide more meaningful counsel, you just need to provide more information about (a) the sort of work you're doing in your NSURLConnectionDataDelegate methods; and (b) which technology you're using to run your code in the background.
For a few more options, also feel free to see GCD and async NSURLConnection.
On iOS, can you make a synchronous network request (off the main thread) and get progress callbacks (on a separate, non-main thread)?
I have have a serial (one-operation-at-a-time) background queue that runs all of time-consuming jobs that don't need to finish right now. I do want to show progress for the download jobs though. It doesn't look like you can instantiate an NSURLConnection and configure a delegate, start synchronous connection, and then get progress callbacks.
Is there a way to make a synchronous request on that background queue (synchronous in that the job behind it doesn't start until its done), and still get setProgress: callbacks which could be sent to update a progressbar? (Callbacks would have to be on a different queue thread, since my serial queue's thread is blocked until the request is finished.)
Apple's docs for NSURLConnection say that the synchronous request is actually built on top of the asynchronous behind the scenes. Do I have to re-implement that? I need a way to block a thread until the request finishes/fails. The best leads I have so far are NSOperationQueue's waitUntilFinished method, but I don't want to start async and continually poll on the synchronous method.
NSURLConnection Discussion
A synchronous load is built on top of the asynchronous loading code made available by the class. The calling thread is blocked while the asynchronous loading system performs the URL load on a thread spawned specifically for this load request. No special threading or run loop configuration is necessary in the calling thread in order to perform a synchronous load.
Reimplementing a synchronous request on top of an asynchronous request is not that hard. You just need to manually spin the thread's run loop until you see that the request has finished (or failed). Here's a rough example:
NSURLRequest *downloadRequest = [NSURLRequest requestWithURL:queryURL];
// Start the download and wait until it finishes
self.downloadFinished = NO;
self.downloader = [NSURLConnection connectionWithRequest:downloadRequest delegate:self];
while (!self.isDownloadFinished)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
Here are the relevant NSURLConnectionDelegate methods:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;
{
self.downloadFinished = YES;
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
{
NSLog(#"An error occurred: %#", error);
self.receivedData = nil;
self.downloadFinished = YES;
}
I'm looping through a list of dates and making a request to a web server for each date in the list.
I would like each date to be processed completely before the subsequent request is sent to the server. To do this, I have set up a serial dispatch queue using GCD. Each time through the date loop, a block is added to the queue.
The problem I am having is that my NSURLConnection is set up using the standard asynchronous call. This results in requests not blocking any subsequent requests. They are thus overrunning each other.
My question: Is this a case where it would make sense for me to use the synchronous NSURLConnection (within the dispatch queue) or is there some other way to make it work using the standard asynchronous call?
There are number of ways to do this. Whatever method you choose, starting the connection needs to be tied to completion of your processing task.
In each block you add to your serial queue, use a synchronous request. This is probably the quickest solution given your current implementation as long as you're ok with the limited error handling of a synchronous request.
Don't use a serial queue. Start the first asynchronous connection and process the response. When processing is complete start the next asynchronous connection. Rinse and repeat.
I think that using the synchronous NSURLConnection API is a fine idea. You have a few other options. One would be to write a wrapper object around NSURLConnection that used the asynchronous NSURLConnection APIs, so you get the nice information that the asynchronous API callbacks provide, including download progress, you can easily continue to update your UI while the request is happening, but which presents its own synchronous method for doing whatever it is you need to do. Essentially, something like:
#implementation MyURLConnectionWrapper
- (BOOL)sendRequestWithError:(NSError **)error
{
error = error ? error : &(NSError *){ nil };
self.finishedLoading = NO;
self.connectionError = nil;
self.urlConnection = [][NSURLConnection alloc] init...]
while (!self.finishedLoading)
{
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]];
}
if (self.connectionError != nil)
{
*error = self.connectionError;
return NO;
}
return YES;
}
#end
(This is all typed off the top of my head, and is heavily abbreviated, but should give you the basic idea.)
You could also do something like fire off each request in the completion delegate method for the previous one, forgoing the use of a serial dispatch queue altogether:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;
{
[self sendNextRequest];
}
Either way, you need to think about how to handle connection errors appropriately. I've used both approaches in different places with good success.
How can I make my program wait for an asynchronous NSURLConnection to finish before going to the next line of code?
SetDelegate *sjd= [SetDelegate alloc];
NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:post delegate:sjd];
[connection start];
This is how I start the connection and I handle the data received in the delegate but I want to wait for the connection to end before proceeding mainly because this is in a for loop and it has to run for each element in my database.
I need to put data from the phones database to a remote database and after the data was successfully put in the data in the phones database is deleted. I am going through each element in the phone's database and start a connection that's why I don't see how the next stuff can be done from the loop. I'm a beginner when it comes to objective-c programming so I'm not sure if this is the right way or not to do it
Making the call synchronous is not an option because it blocks the program and i have a progress bar that should show.
Your question is a bit odd. You have impossibly constrained the issue. You cannot have a line of code "wait" for a process to finish w/o it blocking something, in this case whatever thread the loop is running in.
You can use a synchronous call if you wanted to, it doesn't block your app, it only blocks the thread it is executed on. In your example, you have a loop that is continually getting remote data and you want your UI to reflect that until it is done. But you don't want your UI blocked. That means, this thread with your loop already MUST be on a background thread so you can feel free to do a synchronous call in the loop w/o blocking your UI thread. If the loop is on the UI thread you need to change this to do what you want.
You could also do this using an asynchronous connection. In that case, your operation may actual complete faster b/c you can have multiple requests in progress at the same time. If you do it that way, your loop can remain on the UI thread and you just need to track all of the connections so that when they are finished you can communicate that status to the relevant controllers. You'll need iVars to track the loading state and use either a protocol or NSNotification to communicate when loading is done.
EDIT: ADDED EXAMPLE OF SYNCHRONOUS CALL ON BACKGROUND THREAD
If you want the loop to finish completely only when all requests are finishes and not block your UI thread here's a simple example:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// post an NSNotification that loading has started
for (x = 0; x < numberOfRequests; x++) {
// create the NSURLRequest for this loop iteration
NSURLResponse *response = nil;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
// do something with the data, response, error, etc
}
// post an NSNotification that loading is finished
});
Whatever objects need to show loading status should observe and handle the notifications you post here. The loop will churn through and make all your requests synchronously on a background thread and your UI thread will be unblocked and responsive. This isn't the only way to do this, and in fact, I would do it using async connections myself, but this is a simple example of how to get what you want.
If you just want to know when it's complete and don't really care about any data, simply use the NSNotificationCenter to post a notification and have your view subscribe to it.
Delegate - Post Notification upon completion
-(void) connectionDidFinishLoading:(NSURLConnection*)connection {
[[NSNotificationCenter defaultCenter] postNotificationName:#"NSURLConnectionDidFinish" object:nil];
}
View - Add observer and run some code when observed
-(void) viewDidLoad {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(yourCleanupMethod)
name:#"NSURLConnectionDidFinish"
object:nil];
}
-(void) yourCleanupMethod {
// finish up
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Now, if you need to pass a simple object back as data you can try loading up the object parameter in your notification like this:
[[NSNotificationCenter defaultCenter] postNotificationName:#"NSURLConnectionDidFinish" object:yourDataObject];
Then change your view and cleanup signature like this:
-(void) viewDidLoad {
// Notice the addition to yourCleanupMethod
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(yourCleanupMethod:)
name:#"NSURLConnectionDidFinish"
object:nil];
}
-(void) yourCleanupMethod:(NSNotification *)notif {
// finish up
id yourDataObject = [notif object];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Now I found myself needing something a little more than this so I ended up creating a singleton to handle all of my requests. Since all of your delegate methods in NSURLConnectionDelegate give you and instance of the NSURLConnection for the specific connection, you can simply store a mutable data object in a dictionary and look it up each time by the connection. From there I have a method signature that takes and object and selector in that I associate with the connection so after everything has wrapped up, I can pass that mutable data object to the requestor by performing the selector on that object.
I won't include all of that code here but maybe that will help get you thinking about what is possible. I found that I had a lot of code tied up in making web service calls so wrapping everything up in a singleton gave me a nice clean way of getting data. Hope this helps!
If you really want it to wait, why use an asynchronous call at all? Use a synchronous call instead:
NSURLResponse* response = nil;
NSData* data = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:&response error:nil]
This approach will block the thread it's executed, so you should be sure you want to do it! Did I mention that it will block? :)
You say you want to wait for an asynchronous call to complete, so I'm assuming you're calling the code you posted up in a separate thread.
I would recommend having a look at the new sendAsynchronourRequest method. I've posted up an example of how you can wrap this up in a class which would inform its delegate when the connection has completed / timed out / failed. I'm only referring you to this post because it sounds like you're trying to achieve something very similar to what I was at the time, and this DownloadWrapper class worked flawlessly for me. It's new in iOS5, mind you.
This golden nugget helped me!
I was using synchronous NSURL just fine until I decided I needed SSL for my connection between my client and my server. I took the approach of key pinning which is comparing the cert on the device to the cert on the server (read more on link above) and in order for it to work I needed to add code to the NSURL methods, which from my research you can't do with NSURL synchronous.
Until I found this ridiculously simple solution which worked for me:
NSString *connectionRunLoopMode = #"connectionRunLoopMode";
NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:urlRequest delegate:urlConnectionDelegate startImmediately:NO];
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
[connection unscheduleFromRunLoop:currentRunLoop forMode:NSDefaultRunLoopMode];
[connection scheduleInRunLoop:currentRunLoop forMode:connectionRunLoopMode];
[connection start];
while ([currentRunLoop runMode:connectionRunLoopMode beforeDate:[NSDate distantFuture]]);
NSURLConnection is already asynchronous. Simply implement the delegate methods. If you want to update the UI on the MainThread (e.g. a progress bar), you can do so in didReceiveData.
or look at this
I am currently migrating a project that used ASIHTTPRequest and SBJson to RestKit.
The previous implementation was using an NSOperation to make the HTTP Request, parse the JSON object and make the necessary calls to the Core Data API.
I have re-factored this as follows:
#implementation UpdateBeers
#pragma mark - NSOperation
- (void)main {
[[RKClient sharedClient] get:#"/beers" delegate:self];
}
- (void)request:(RKRequest*)request didLoadResponse:(RKResponse*)response {
debug(#"didLoadResponse");
}
- (void)request:(RKRequest *)request didFailLoadWithError:(NSError *)error {
debug(#"%#", error);
}
#pragma mark - Memory
- (void) dealloc {
[super dealloc];
}
#end
The following appears in the log
sending GET request to URL http://localhost:9091/api/test. HTTP Body:
The problem is the server never receives the request.
Adding the following line :
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.3]];
to the end of the main method solves this problem.
My question is :
Should I be executing ResKit API calls as an NSOperation and if not what are my alternatives for making calls in the background?
Thanks in advance.
Perhaps I'm not fully understanding the overall problem here ... Just about everything RestKit does with respect to loading resources from the network is already in the background. Almost everything is already asynchronous, and thus running the existing asynchronous RestKit methods inside an NSOperation is duplicative and counter productive. That being said, once RestKit tells you that it's finished downloading your data (didLoadResponse), you may want to do any subsequent post-processing in an NSOperation if that part is computationally intensive and unrelated to the UI. Otherwise, don't try to outthink RestKit, just run it as in the examples and you're good to go with asynchronous goodness.
Moreover, you may want to look at using RestKit's request queues if you plan to fire off several requests at the same time. It'll still download everything asynchronously, but it'll only run as many requests at a time as you tell it ... when my app is updating, send of about seven requests at once, but the queue will run them serially instead of parallel, thus preventing any network bandwidth issues