0

I am having a bit of trouble with displaying the progress of a longer mainthread action (needs to be in the main thread).

The action is called by pressing a button.

-(void)getCSVExport:(id)sender{
    ...
    NSString *filePath = [path stringByAppendingPathComponent:fileName];
    NSData *csvData = [NSData dataWithContentsOfFile:filePath];
    if (nil == csvData) {
       _progressView.hidden = NO;
       [self.view bringSubviewToFront:_progressView];
       _progressView.progress = 0;
       csvData = [self generateCSVExportForMonth:_monthToExportInt];
       [csvData writeToFile:filePath atomically:YES];
       _progressView.hidden = YES;
    }
    ...
}

within the funktion generateCSVExportForMonth: i am updating the progress with _progressView.progress = newValue.

i now have 2 problems:

1) when pressing the button that calls getCSVExport: the button remains highlighted until the call is finished. 2) the progressView does never show up, let alone update itself.

information: the call takes between .5 and 2 seconds depending on the device.

any ideas where i've gone wrong?

// EDIT: new version with backgroundThread:

[self.view bringSubviewToFront:_progressView];
_progressView.progress = 0;
[self performSelector:@selector(assignCSVData:) onThread:[NSThread new] withObject:csvData waitUntilDone:YES];
_progressView.hidden = YES;

and the time expensive call:

-(void)assignCSVData:(NSData*)data{
    data = [self generateCSVExportForMonth:_monthToExportInt];
 }

this results in a deadlock upon the performSelector call.

Sebastian Flückiger
  • 5,525
  • 8
  • 33
  • 69

1 Answers1

2

The problem is that your main thread is blocked. That's exactly the reason for using background threads for things that take longer, because you don't want the GUI to freeze.

I don't see why something like parsing a CSV file absolutely has to be on the main thread. You'll have to do it in the background (or live with the bad user experience of a frozen GUI).

You have a few options how to actually implement something like this.

  • Use NSObject's performSelectorInBackground:withObject:

    Put the parsing code in a separate method and start it on a background thread using:

    [self performSelectorInBackground:@selector(parseMethod) withObject:csvData];
    

    At the end of that method you call some method on the main thread to notify it that the parsing is finshed.

    [self performSelectorOnMainThread:@selector(parsingDone:) withObject:result waitUntilDone:NO];
    
  • Use Grand Central Dispatch (GCD) to run some code in the background using the block syntax. Also quite simply, but a little bit more complicated syntax and semantics wise, if you're used to Objective-C and the Cocoa APIs.

  • Use NSOperation and NSOperationQueue. Probably a little bit of an overhead for your purpose. Though you can also easily add a new operation to a queue by calling addOperationWithBlock without subclassing NSOperation.

DrummerB
  • 39,814
  • 12
  • 105
  • 142
  • my problem is that the method generating the csv file accesses coredata. ive had deadlocks because of that in the past and want to avoid it. letting the user wait for max 2 seconds once in the lifetime of the app - the report will never be recreated but stored and reused - was my plan to ensure stability. the other way is that i would need to ensure that the application stays in the very same place (viewcontroller) until the report is generated because it will be used just there :/ if there is a clever way of doing that i would be glad as well :) – Sebastian Flückiger Apr 03 '13 at 12:18
  • Is it constantly relying on Cora Data objects? Can't you just fetch those objects you need and pass them to the parser in the background? – DrummerB Apr 03 '13 at 12:24
  • the report takes exactly 5+(numberOfDaysOfMonth) fetches, constantly spread over the function execution. The fetches vary highly between object and property fetches. to fetch everything at once would in a big performance loss due to tedious sorting of the results. – Sebastian Flückiger Apr 03 '13 at 12:29
  • suggesting i could make it threadsafe. how would i go about calling `csvData = [self generateCSVExportForMonth:_monthToExportInt];` in another thread? this is the time expensive call. – Sebastian Flückiger Apr 03 '13 at 12:30
  • You need to be somewhat careful with Core Data and background threads. *Never* pass an actual `NSManagedObject` to a different thread (only the object ID). Better yet, create a separate `NSManagedObjectContext` in the background thread. But if this really only takes 2 seconds at most, you can probably get away with doing it on the main thread and updating the progress bar with some [run loop trickery](http://stackoverflow.com/a/4766028/573626)... – omz Apr 03 '13 at 12:34
  • i have added a bit more code trying to perform the time costly call in the background - sadly it does not work as simple as i'd hoped :/ – Sebastian Flückiger Apr 03 '13 at 12:34
  • @omz thank you for that link, that looks promising. in all my tests i have never exceeded 0.9 seconds but assuming that some random user will have the double amount than my test data (which was rediculously big) i dont believe 2 seconds is possible ;) thanks for the tip! – Sebastian Flückiger Apr 03 '13 at 13:18
  • @DrummerB thank you for the updated answer :) i think the missing callback was my problem. thank you for your time. – Sebastian Flückiger Apr 03 '13 at 19:53