I have a basic architectural question about how to handle asynchronous downloading or really any program that has a chain of events, and each event doesn't start until a prior event finishes, however long it takes.
If I am going to download data, and then perform an action after the download completes, but in the meanwhile I wish to do something else (like show a progress indicator, a splash screen or something else), would it make sense to design the program like so:
-(void)thisMethodStartsTheDownloading{
//start the download using NSURLConnection (I'm fine with these details)
//show a UIView for a splash screen image or do anything else you want to do
}
-(void)thisMethodRunsWhenDownloadIsFinished{
//hide or remove the splash screen (if applicable)
//do whatever I need to do with the downloaded data
//this method does not run on its own unless called when NSURLConnection is done
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
//do whatever I need to do with my downloaded data
[self performSelector:#selector(thisMethodRunsWhenDownloadIsFinished)];
// or [self thisMethodRunsWhenDownloadIsFinished];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
//incrementally appendData as it comes in from the download process
[receivedData appendData:data];
}
I want to make sure I understand the structure of a program that jumps around between methods as each method completes.
Basically, I presume that when you have a chain of processes, but each part of that chain can't run unless the prior chain is finished, you break up your chain into different methods like I've done here, linking them to each other by calling the next one in the chain when the current one finishes. Is that the ideal/standard way of doing this?
Yes.
A standard and robust way to architect a program is to organize all code as a response to an event. Your terminology ("chain of processes") is unfamiliar to me, but it sounds like you are asking if this is the way to translate a sequence or script (how you want things to go) into an event-based environment.
The truth is, sometimes the data doesn't arrive from the network, or not all of it does, or it takes a long time. So thinking of "download data" as a step in a multistep process hides a lot of problems that a real app needs to deal with.
If your step-by-step process is this:
Show Progress Bar
Start Download
Loop: get data, update progress bar
Hide Progress Bar
Do thing with data
So yes, you translate it to events and reactions:
The app finished launching: show progress bar, start a download
First data arrived: initialize buffer, set progress bar to zero
More data arrived: add to buffer, updated progress bar
The connection was broken: display alert (try again, cancel)
User tapped try again: start download
User tapped cancel: remove progress bar, show something else
The download is complete: hide progress bar, do something else
While the first way is easier to follow as a narrative, the second way does a better job of handling problems and edge cases (for example, a HTTP redirect causes a "first data arrived" event to happen twice).
Take a look at https://github.com/jdg/MBProgressHUD.
You have the idea correct. You simple chain each call to the next, similar how work a UINavigationController.
Using MBProgressHUD with something like http://allseeing-i.com/ASIHTTPRequest/ will give you almost all the things you will need for this kind of task.
Take a look in how is done using the standar libs on:
https://github.com/jdg/MBProgressHUD/blob/master/Demo/Classes/HudDemoViewController.m
Related
I've looked at a lot of topics but I still can't figure it out.
I have a UITableview which downloads its content online. Each cell has an image, and I use GCD to let the image download. The downloaded image will be saved to disk, and before each time a cell is loaded there is checked if the file already exist, if not -> gcd, nsdata etc.
All goes well if someone has a good internet connection (wifi), but if I'm going to hop from View to View (back and forth), with my crappy 3G connection, what happens is that it wants to finish its queue (about 4 cells), but already gets assigned a new one, and a new one, and a new one and eventually the user has to wait a looong time before the others are executed (which he doesnt see) before the actual UITableview gets populated. With NSLog I can see that even I'm in a different view, it's still downloading and making uiimages that were visible on the screen. Each task is approximately 100 kb, and with a slow (or even no internet connection?!) it can take a while if you have a lot.
I know it's not possible to cancel it, but I read in other topics about using a BOOL variable but I don't really get it. Even if the BOOL variable change when the user leaves the screen, the cells are already in queue right?
Is it possible that when a user taps the back button in my Navigationcontroller, so he leaves the view, I change the data the blocks in queue use (empty it), so there is nothing to download and the blocks will be executed right away (there is nothing to do). So something like, making every value in array newsitems nil? Is it possible to change the datasource, or will the blocks that are waiting already have their datasource with them while waiting?
Then there is another problem, this doesn't have effect on the the currently executed block.
Can someone point me in a good direction?
Thank you.
Prastow
You can make use of NSBlockOperation and NSOperationQueue to create a cancellable download task. You create an NSBlockOperation by giving it a block which performs some work. In your case the block would download the contents of the URL.
In your view controller, you would store a list of the operations that have been submitted to the queue. If the user decides to leave the current view, you can then call cancel on each of the pending operations to prevent any needless work from taking place. The currently running operation will run to completion however. In order to cancel the currently running operation, you need to store a weak reference to the NSOperation object in the block doing teh work. Then at appropriate intervals within the body of the block, you can check to see if the operation has been cancelled and exit early.
// Create a queue on which to run the downloads
NSOperationQueue* queue = [NSOperationQueue new];
// Create an operation without any work to do
NSBlockOperation* downloadImageOperation = [NSBlockOperation new];
// Make a weak reference to the operation. This is used to check if the operation
// has been cancelled from within the block
__weak NSBlockOperation* operation = downloadImageOperation;
// The url from which to download the image
NSURL* imageURL = [NSURL URLWithString:#"http://www.someaddress.com/image.png"];
// Give the operation some work to do
[downloadImageOperation addExecutionBlock: ^() {
// Download the image
NSData* imageData = [NSData dataWithContentsOfURL:imageURL];
// Make sure the operation was not cancelled whilst the download was in progress
if (operation.isCancelled) {
return;
}
// Do something with the image
}];
// Schedule the download by adding the download operation to the queue
[queue addOperation:imageDownloadOperation];
// As necessary
// Cancel the operation if it is not already running
[imageDownloadOperation cancel];
A good talk on this exact topic was given at WWDC this year entitled "Building Concurrent User Interfaces on iOS". You can find the video and slides here
I faced similar issues with an app I developed a while back and found that the best way to do everything you require, and more, is to use https://github.com/MugunthKumar/MKNetworkKit
It took me the best part of a day to learn and understand the conversion and then a couple more days to tweak it to exactly what I needed.
If you do decide to use it or would like a thorough overview of the capabilities start here
http://blog.mugunthkumar.com/products/ios-framework-introducing-mknetworkkit/
I have an app that imports a potentially large amount of data from the web after the user explicitly presses a Sync button, and stores that data using Core Data. Since I want to show feedback and I don't want the user interacting with the rest of the app while this happens, pressing the Sync button brings up a Modal dialog. Since I want the operation to happen immediately, the operation executes in the viewDidAppear method. I'm sure this is frowned upon.
There are a bunch of problems with the approach right now:
Everything happens in the main thread. The user kind of gets feedback because there is an activity indicator that continues to animate, but there's no way to indicate progress or show intermediate messages. This is not the right way to do things.
But, I am told that when using Core Data, everything has to use the main thread, so breaking off the work into another thread does not seem like it will be straightforward.
If the app enters the background state (user hits Home button or iPad falls sleep), it's game over - the operation dies. It's clear to me from the documentation why this is the case.
I know there are "I'm about to enter the background" events that you can handle, but it's not as though I can move execution of code from one place to another in the middle of a file download. Whatever solution I use has to be a continuous action that executes in the same way both before and after the transitions to/from the background.
I want the operation to execute in the foreground as far as the user is concerned. It does not make sense for them to interact with other parts of the app while this operation is taking place.
I am reading the Apple documentation on this, but I'm asking this in hopes of finding more concise guidance on this particular combination of needs. Thanks.
You really should not freeze the main thread. You can still "prohibit" certain UI actions.
Create a separate context, as a child, and do all your work in there. When done (or at certain intervals), save the context to the main context, and notify the main thread to do some UI update interaction... maybe a progress bar or something...
NSManagedContext *backgroundContext = [NSManagedContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
backgroudContext.parentContext = [self mainManagedObjectContext];
[backgroundContext performBlock:^{
// This block is running in a background thread.
// Go get your data from the web
// Call this to push data to the main MOC (either at end, or at intervals)
[backgroundContext save:&error];
// When you want to do something on the main thread...
dispatch_async(dispatch_get_main_queue(), ^{
// This block is running on the main queue... I can do anything with the UI...
}];
}];
Couple of things to note... your mainMOC needs to be private or main queue concurrency type. If you are using the Core Data template, where it is in the app delegate, just change the alloc/init to initWithConcurrencyType:NSMainQueueConcurrencyType.
I would, however, suggest using the canonical main/parent relationship. Create a private MOC, assign it to the persistent store, then create a main MOC, set its parent to be that private MOC. Now you are ready to handle any I/O with background operations, without blocking your UI.
Still, when loading from the web, use the pattern above: create a child MOC, then load objects into the main MOC.
Note, that the data is not saved to disk until the "root" MOC calls save.
I need an advice what to do when my view controller is loading quite long?
In my situation I have an offline map made with route-me and it takes several seconds to load the map from about 100mb database, then load a lot of markers, put them on the map, etc.
If I run this code in viewDidLoad UI seems unresponsive, because after pressing on tab or button nothing happens for a few seconds while everything is loading. If I put it in viewDidAppear map somehow doesn't get shown at all until I quit this view controller and go back to it.
If it takes so long you should show a spinner or other "busy" indicator while you load your data on a background thread.
Once your time-intensive process is complete, update the UI back on the main thread and hide your spinner/busy indicator.
Executing code on a background thread is extremely easy - there are several ways to do it - but the easiest/most straight-forward way is probably with performSelectorInBackground:withObject: as in this example:
[self performSelectorInBackground:#selector(loadMap) withObject:nil];
When you're ready to run on the main thread again - it's the same thing, but in reverse using performSelectorOnMainThread:withObject:waitUntilDone::
[self performSelectorOnMainThread:#selector(wrapupLoadMap) withObject:nil waitUntilDone:NO];
Good luck.
I have encountered a strange bug in my application, and I am trying to debug it using step execution.
However it seems that things on an iphone often do not happen as synchronously as I would like, for example when I step-over this line
[self.view addSubview:FinndomoEmbeddedMWView.view];
nothing happens in the emulator.
If I just let the program run, the view is added and the screen changes as it should.
So I am guessing, addSubview does not do everything related to adding a view, it just sort of starts the process, and then it is completed later.
I don't know if there are message queues on ios similair to winapi, but there must be something like that, so is there a function for "processing all accumulated messages". I would then like to call this function after my addSubview and actually see the things change in the emulator while I debug, not when the program is running.
And I experience this not only with addSubview, so I want to have a general solution for things like this.
Sat yesterday and found out the answer:
NSDate *dtr = [[NSDate alloc] initWithTimeIntervalSinceNow:0.5];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:dtr];
This is sort of what I wanted. As you can see, this particular code may end up waiting for 0.5 seconds if there are no more events, and if there are events pending, it is only processing the first one and then returns (according to documentation).
But that could be avoided of course, this is just an example, if anyone will want the same thing. In my case there was only 1 important event, and so the provided snippet worked.
The short answer is no.
Cocoa is event-driven.
The core of each app is the event loop. On each pass through the event loop, the app handles events added to the event queue. Updating the screen is one such event.
Thus, changes to screen display don't take place until after your code returns, on the next pass through the event loop.
For debugging purposes where you want to figure out what's happening line-by-line, you need to either use the debugger's facilities, or add NSLog statements to your code.
I am trying to solve a problem in Objective-C, but I don't think the question is language specific.
I have to do some processing down in a model class that has no notion of UI. However, this processing takes some time and I want to let the user know the status via a progress bar.
My first attempt at this was defining a notion of a progress handler protocol/interface with some methods like
-startOperation;
-updateProgress:(double)currentValue ofMax:(double)maxValue
-endOperation;
This way my UI can implement that the the model need not know details about what goes on other than someone wants progress updates. Currently my UI unhides a progress bar, and updates it, then hides it when done. So far so good.
However, it turns out that sometimes this operation processing is very fast. Such that the UI updates result in a pretty disconcerting flicker as they execute. I don't know if the operation will be fast or slow beforehand.
One idea I had was to force the operation to take at least a certain duration to avoid the UI changes being so jarring to the eye, but this seemed to put knowledge of the UI in the model class, which must be wrong.
This would seem to be a common issue with (hopefully) some known pattern.
How would you address this?
Jonathan's and Darren's answers cover your actual problem, but I would add something regarding the question in the title: "How should the model update the UI of its progress?"
The answer, of course, is that it shouldn't. The model shouldn't have to know anything about any protocols for displaying data. There should be one uniform bindings layer taking care about propagating information from the model to the interface. Fortunately, Cocoa already includes such a bindings mechanism: Key-Value Observing.
What you should do is define a property on any model class where the concept of progress makes sense, something like #property (assign) float progress. Then you make sure the class is KVO compliant. Controller code that want to keep track of the progress simply registers to observe this value with something like:
[theObject addObserver:self forKeyPath:#"progress" options:0 context:NULL];
Make sure to read the documentation for the NSKeyValueObserving (KVO) informal protocol.
Also, you might want to have a look at Mike Ash's KVO-related notes and code: Key-Value Observing Done Right.
You can use NSTimer to delay the display of your progress bar until your operation had run for a given amount of time, say half a second:
-(void)startOperation {
// Show the progress bar in 0.5 seconds
if (!_timer) {
_timer = [[NSTimer scheduledTimerWithTimeInterval:0.5
target:self
selector:#selector(showProgressBar:)
userInfo:nil
repeats:NO] retain];
}
}
In -endOperation, you cancel the timer and hide progress bar:
-(void)endOperation {
[_timer invalidate]; // cancel the timer
[_timer release];
_timer = nil;
[self hideProgressBar];
}
If the operation completes in less than 0.5 seconds, the timer is canceled before the progress bar is displayed.
One thing commonly done is to have your progress bar implementation not show itself right away, and apply some heuristic based on the first couple of updates (or a timeout) to determine whether it needs to show itself at all. That's how the Java ProgressMonitor behaves, for example. (The ProgressMonitor is a nice abstraction that separates the knowledge of progress from its graphical representation).
Once the progress widget is showing, you could repaint it on a leisurely timer, say 10 times per second, rather than reacting to every progress change event with a repaint.