3

the question is simple : my app control if there is an update every time it starts. If there is an update a popup will be shown with a Yes or No choose. When user tap Yes 4 methods start. These methods download xml file and upload CoreData. This is the code of the alert :

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {

    if (buttonIndex==1) {
        [self showActivityViewer];
        [self downloadControlAndUpdatePoi];
        [self downloadControlAndUpdateItinerari];
        [self downloadControlAndUpdateArtisti];
        [self downloadControlAndUpdateEventi];
        [self hideActivityViewer];
        NSLog(@"AGGIORNA");
    } else {
        NSLog(@"NON AGGIORNARE");
        return;
    }
}

But there is a problem : when user tap Yes the alert doesn't disappear and remain on screen until all methods are finished. So i try this other code :

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {

    if (buttonIndex==1) {
        [self showActivityViewer];
        [NSThread detachNewThreadSelector:@selector(startDownloads) toTarget:self withObject:nil];
        [self hideActivityViewer];
        NSLog(@"AGGIORNA");
    } else {
        NSLog(@"NON AGGIORNARE");
        return;
    }
}

-(void)startDownloads {
    NSInvocationOperation *opPoi=[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadControlAndUpdatePoi) object:nil];
    NSInvocationOperation *opItinerari=[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadControlAndUpdateItinerari) object:nil];
    NSInvocationOperation *opArtisti=[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadControlAndUpdateArtisti) object:nil];
    NSInvocationOperation *opEventi=[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadControlAndUpdateEventi) object:nil];
    NSArray *operations=[[NSArray alloc] initWithObjects:opPoi,opItinerari,opArtisti,opEventi, nil];
    NSOperationQueue *queue=[[NSOperationQueue alloc] init];
    [queue addOperations:operations waitUntilFinished:YES];
    [queue waitUntilAllOperationsAreFinished];

}

There is a problem even here : i tap start, but the activity viewer doesn't appear. The alert disappear and the thread start and run the 4 methods one after another.

I need the processes run in background, just like happened with my 2nd code, but i need even my showActityViewer method will be run and show the spinner.

Thanks :)

Andrea Mario Lufino
  • 7,921
  • 12
  • 47
  • 78

1 Answers1

5

First things first. You don't need to start 4 operations, since you are already in a secondary thread and do not need that the 4 operations be executed in parallel. You could simply do:

-(void)startDownloads {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];  
    [self downloadControlAndUpdatePoi];
    [self downloadControlAndUpdateItinerari];
    [self downloadControlAndUpdateArtisti];
    [self downloadControlAndUpdateEventi];
    [pool release];
}

Above all, you need define an autorelease pool in startDownloads if you use autorelease in the downloadControl* methods, otherwise I suspect you will have leaks.

As to why the activity indicator does not show up, it depends on the fact that you are calling:

    [self hideActivityViewer];

immediately after detach. So, you are showing it and removing it, before event the UI has got the time to update itself. Remove that line from there and rewrite startDownloads like this:

-(void)startDownloads {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];  
    [self downloadControlAndUpdatePoi];
    [self downloadControlAndUpdateItinerari];
    [self downloadControlAndUpdateArtisti];
    [self downloadControlAndUpdateEventi];
    [self performSelectorOnMainThread:@selector(hideActivityViewer) withObject:nil waitUntilDone:NO];  

    [pool release];
}

Here notice that I am calling into the main thread to hideActivityViewer because only the main thread can safely use UIKit. EDIT:

I did not know that you were using Core Data in the download methods...

Have a look at Concurrency with Core Data. You will need to tweak a bit your code by at least using a separate managed object context for your secondary thread (I don't know if it is feasible for you to create the moc there).

Also have a look at this tutorial from Cocoa is my Girlfriend.

As an alternative to all that, you could consider doing:

if (buttonIndex==1) {
    [self showActivityViewer];
    [self performSelector:@selector(startDownloads) withObject:nil afterDelay:0];
    NSLog(@"AGGIORNA");
} else {
    NSLog(@"NON AGGIORNARE");
    return;
}

with:

-(void)startDownloads {
    [self downloadControlAndUpdatePoi];
    [self downloadControlAndUpdateItinerari];
    [self downloadControlAndUpdateArtisti];
    [self downloadControlAndUpdateEventi];
    [self hideActivityViewer];  
}

This does not use threads at all, but I am not sure that the activity viewer will show without any glitch. One more level of hack, if needed, and you could specify a delay in

[self performSelector:@selector(startDownloads) withObject:nil afterDelay:0.1];
sergio
  • 68,819
  • 11
  • 102
  • 123
  • but how have i to call startDownloads? With NSThread detach or [self startDownloads] after user tap Yes? – Andrea Mario Lufino Aug 04 '11 at 09:11
  • Sorry, with detachThread; you are doing this correctly. the only point is, in startDownloads you do not need more async operations, they can just be sequential. – sergio Aug 04 '11 at 09:14
  • Seems it is all ok, but one problem, big problem, occurred : my downloadAndControl* methods work with CoreData and with this solution i have an NSInternalInconsistencyException with method save of CoreData. – Andrea Mario Lufino Aug 04 '11 at 09:19
  • Good, solved all!!! The methods that use CoreData must be run by the main thread, so i use performSelectorOnMainThread to run these methods. Thanks thanks thanks to sergio for his awesome answer! :) – Andrea Mario Lufino Aug 04 '11 at 10:07