switching views with grand central dispatch - objective-c

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/

Related

Status application: sometimes panels are missing, multithreading possible issue

I have a status application, it's long to post it all so I'll describe it and post only part of the code:
In the xib file there are two objects: AboutController and PreferencesController;
The app delegate is able to launch AboutController's and PreferencesController's panels;
The panels are in the xib file too;
The user by selecting a status menu item is able to launch these two panels;
There is a timer that constantly downloads a HTML page and reads it;
When the page is downloaded, the stringValue of a label is changed.But the stringValue may also be changed from the PreferencesController.The page is downloaded from a background thread, but it's changed through the main queue.
Now I've got some questions:
Do I have to invalidate the timer when the application starts sleeping (computer goes in standby),and create another one when it returns on?
The label is updated in the main queue, so I still have to protect the label access with a mutex?
Sometimes the panels are missing: at the start of the application I am able to launch panels by clicking a menu item, but sometimes they are not launching.I don't know how to reproduce this bug always, it just happens randomly when the application is active for 2/3 hours usually, I have to relaunch the application to go around this.
The code is too long, that's only a piece of code:
- (void) checkPosts: (id ) sender
{
NSOperationQueue* queue=[NSOperationQueue new];
queue.maxConcurrentOperationCount=1;
[queue addOperationWithBlock:^
{
NSNumber* newPosts= [self updatePosts];
NSNumber* posts= [controller posts];
if([posts integerValue]!=[newPosts integerValue])
{
NSOperationQueue* queue=[NSOperationQueue mainQueue];
posts= newPosts;
[queue addOperationWithBlock:^
{
// This is where I may have a race condition
item.attributedTitle=[[NSAttributedString alloc]initWithString: [formatter stringFromNumber: posts] attributes: #{NSForegroundColorAttributeName : [controller color], NSFontAttributeName : [NSFont userFontOfSize: 12.5]}];
}];
// That's not so relevant:
NSUserNotification* notification=[NSUserNotification new];
notification.title= [NSString stringWithFormat: #"posts Changed to %#",posts];
notification.deliveryDate=[NSDate date];
notification.soundName= NSUserNotificationDefaultSoundName;
NSUserNotificationCenter* center=[NSUserNotificationCenter defaultUserNotificationCenter];
[center deliverNotification: notification];
center.delegate= self;
[controller setPosts: posts];
}
}];
}
A little background information:
This method works in a background thread;
[self updatePosts] downloads the HTML page and returns the number of posts;
[controller posts] reads the previous number of posts using NSUserDefaults;
item is a status menu's menu item.
More Details
This is how I get the timer:
// In the applicationDidFinishLaunching method
timer=[NSTimer scheduledTimerWithTimeInterval: [interval integerValue ] target: self selector: #selector(checkReputation:) userInfo: nil repeats: YES];
timer is a property:
#property (nonatomic, strong) NSTimer* timer;
interval is a NSNumber, for sure it's integer value is greater or equal than 1.
It's not entirely clear what's happening here. You've provided plenty of information but not everything needed to give a definitive answer. I'll try to address your questions first:
Do I have to invalidate the timer when the application starts sleeping
(computer goes in standby),and create another one when it returns on?
Have to? No. Should for cleanliness and certainty of the state? Yes, probably. You should probably specify exactly how you set up the timer since you can run into problems with it interacting with the run loop ... but I don't think this is your problem.
The label is updated in the main queue, so I still have to protect the
label access with a mutex?
As long as you update the UI from the main thread/queue, you should be fine. This is a standard design approach with blocks.
Sometimes the panels are missing: at the start of the application I am
able to launch panels by clicking a menu item, but sometimes they are
not launching.I don't know how to reproduce this bug always, it just
happens randomly when the application is active for 2/3 hours usually,
I have to relaunch the application to go around this.
If you don't know how to reproduce it, I'm not sure we can help you beyond "places to look." The first thought I've got is that you may be recreating multiple copies of your primary controllers when the app becomes active (since you asked about this earlier, I assume you've tried doing something with it). Make sure the same controllers are being reused.
Now on to the code.
NSOperationQueue* queue=[NSOperationQueue new];
The queue variable is local to the scope of the method. I see no retain/release, so I assume you're using ARC. In that case, you're not retaining the new queue you're creating and its life span is not guaranteed to survive as long as you need it to once the method completes and you've left its scope. You should make queue an instance variable so it sticks around. This way the queue can be reused every time the method is fired and it'll stay around long enough for other queues/threads to use.
I think it's likely this is your biggest culprit. Adjust it and update your question to reflect how it affects the condition of your app.

Best practice for a long-running foreground operation that uses Core Data?

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.

how can we use combination of NSThread and NSNotification?

I am doing an application where images from the user are taken all together and saved in NSMutableArray.
As soon as even one image has been start coming, I need to upload images to server one by one though images are taken together
I am using [NSThread detachNewThreadSelector:#selector(uploading:) toTarget:self withObject:imagearray]; to upload images one by one. I need to show progressview to user as images are being uploaded one by one.
How do I notify after one image has been uploaded?
Or is there any other scenario that is useful for this more than NSThread+NSNotification?
I sugest to use something similar to "delegate" paradigm but thinking on threads instead of objects. So the uploading thread delegates on main thread as it is the one to make user interface changes.
For example, the uploading thread can send messages for partial upload progress
[self performSelectorOnMainThread:#selector(uploadProgression:)
withObject:foo waitUntilDone:NO]
or for each complete upload finished
[self performSelectorOnMainThread:#selector(uploadDidEnd:) withObject:foo
waitUntilDone:YES]
I suppose that you have not to stop uploading for updating partial progress in progessView but you need to wait when upload ends so not to duplicate uploading threads launching a new upload.
You should use notifications in case you don't know how many listeners it is out there and you just post notification about something. In your case you probably have only one view controller, so there is no need to use notifications so just create some protocol for the delegate and implement it in your view controller. If you need to update your UI, then you should also invoke all delegate methods using performSelectorOnMainThread.

How to speed up saving a UIImagePickerController image from the camera to the filesystem via UIImagePNGRepresentation()?

I'm making an applications that let users take a photo and show them both in thumbnail and photo viewer.
I have NSManagedObject class called photo and photo has a method that takes UIImage and converts it to PNG using UIImagePNGRepresentation() and saves it to filesystem.
After this operation, resize the image to thumbnail size and save it.
The problem here is UIImagePNGRepresentation() and conversion of image size seems to be really slow and I don't know if this is a right way to do it.
Tell me if anyone know the best way to accomplish what I want to do.
Thank you in advance.
Depending on the image resolution, UIImagePNGRepresentation can indeed be quite slow, as can any writing to the file system.
You should always execute these types of operations in an asynchronous queue. Even if the performance seems good enough for your application when testing, you should still do it an asynch queue -- you never know what other processes the device might have going on which might slow the save down once your app is in the hands of users.
Newer versions of iOS make saving asynchronously really, really easy using Grand Central Dispatch (GCD). The steps are:
Create an NSBlockOperation which saves the image
In the block operation's completion block, read the image from disk & display it. The only caveat here is that you must use the main queue to display the image: all UI operations must occur on the main thread.
Add the block operation to an operation queue and watch it go!
That's it. And here's the code:
// Create a block operation with our saves
NSBlockOperation* saveOp = [NSBlockOperation blockOperationWithBlock: ^{
[UIImagePNGRepresentation(image) writeToFile:file atomically:YES];
[UIImagePNGRepresentation(thumbImage) writeToFile:thumbfile atomically:YES];
}];
// Use the completion block to update our UI from the main queue
[saveOp setCompletionBlock:^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
UIImage *image = [UIImage imageWithContentsOfFile:thumbfile];
// TODO: Assign image to imageview
}];
}];
// Kick off the operation, sit back, and relax. Go answer some stackoverflow
// questions or something.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:saveOp];
Once you are comfortable with this code pattern, you will find yourself using it a lot. It's incredibly useful when generating large datasets, long operations on load, etc. Essentially, any operation that makes your UI laggy in the least is a good candidate for this code. Just remember, you can't do anything to the UI while you aren't in the main queue and everything else is cake.
Yes, it does take time on iPhone 4, where the image size is around 6 MB. The solution is to execute UIImagePNGRepresentation() in a background thread, using performSelectorInBackground:withObject:, so that your UI thread does not freeze.
It will probably be much faster to do the resizing before converting to PNG.
Try UIImageJPEGRepresentation with a medium compression quality. If the bottleneck is IO then this may prove faster as the filesize will generally be smaller than a png.
Use Instruments to check whether UIImagePNGRepresentation is the slow part or whether it is writing the data out to the filesystem which is slow.

iphone Asynch downloading and handling multiple processes

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