Question

I'm trying to improve the responsiveness of my app but am a complete newbie to threading and have become confused.

On launch an alert is shown:

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (buttonIndex == 0)
    {
        NSString *user = [[alertView textFieldAtIndex:0] text];
        NSString *pass = [[alertView textFieldAtIndex:1] text];
        [self loginToServerWithUsername:user andPassword:pass];
    }
}

within the loginToServerWithUsername: method, the app calls a method:

[self checkFiles:sessionID];

This can take several seconds so I tried to execute it in the background:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                        [self checkFiles:sessionID];
                    });

checkFiles method:

fileList = [[NSMutableString alloc] init];

NSArray *directoryPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [directoryPaths objectAtIndex:0];
NSString *downloadsFolderString = [documentsDirectory stringByAppendingPathComponent:DOWNLOADS_FOLDER];
NSError *error = nil;
NSString* file;
NSDirectoryEnumerator* enumerator = [[NSFileManager defaultManager] enumeratorAtPath:downloadsFolderString];
while (file = [enumerator nextObject])
{
    BOOL isDirectory = NO;        
    [[NSFileManager defaultManager] fileExistsAtPath: [NSString stringWithFormat:@"%@/%@",downloadsFolderString,file]
                                         isDirectory: &isDirectory];

            if (!isDirectory)
            {
                [fileList appendString:[NSString stringWithFormat:@"%@|", file]];
            }
}
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"dd/MM/yyyy HH:mm"];
NSString *timeOpened = [formatter stringFromDate:[NSDate date]];

NSString *post = [NSString stringWithFormat:@"sessionID=%@&fileList=%@&dateTime=%@&userID=%@", sessionID, fileList, timeOpened, userID];
NSData *postData = [post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString *postLength = [NSString stringWithFormat:@"%d", [postData length]];
NSString *comparisonURLString = SERVER_COMPARE_URL_STRING;
NSURL *comparisonURL = [NSURL URLWithString:comparisonURLString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:comparisonURL];
[request setHTTPMethod:@"POST"];
[request addValue:postLength forHTTPHeaderField:@"Content-Length"];
[request setHTTPBody:postData];
NSHTTPURLResponse *urlResponse = nil;
error = nil;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&urlResponse error:&error];

if (responseData)
{
    NSString *requiredFilesList = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];

    NSArray *lines = [requiredFilesList componentsSeparatedByString: @"\n"];        
    if (lines.count > 2)
    {
        dispatch_async(dispatch_get_main_queue(), ^(void){

            NSRange theRange;
            theRange.location = 2;
            theRange.length = [lines count] -3;
            numberOfFilesToBeDownloaded = theRange.length;

            if (numberOfFilesToBeDownloaded <= 0)
            {
                _jobStatusLabel.text = @"Documents up to date"; // is this the issue?
            }            
            if (numberOfFilesToBeDownloaded > 0)
            {
                NSArray *subArray = [lines subarrayWithRange:theRange];
                [self animateProgressBar];
                if (![eraseDevice isEqualToString:@"true"])
                {
                [self getFiles:subArray];
                }
            }
        });
    }
}
else
{
    NSLog(@"no response data from check files");
}

However, the alertView is not dismissed until the checkFiles method is complete. Can anyone tell me how to have the alert dismiss whilst checkFiles runs in the background?

Was it helpful?

Solution

The UI operations should be done in the main thread.

For instance :

_jobStatusLabel.text = @"Documents up to date"; // is this the issue?

should be

dispatch_async(dispatch_get_main_queue(), ^(void){
    _jobStatusLabel.text = @"Documents up to date"; // is this the issue?
});

change all such possible cases.

OTHER TIPS

Obviously you are updaing UI elemets from background thread. This would lead to unpredictable behavior. A better way to do this is like this -

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
     [self checkFiles:sessionID];

     dispatch_async(dispatch_get_main_queue(),^{
         //breakup your checkFiles method to background tasks & UI tasks. Put UI updating 
         //code here which will get executed in the main thread.
     });
});

This is the GCD pattern of coding. Here it is asynchronous thread executes code synchronously. i.e. after checkFiles finishes the next code (here its a async thread) gets executed.

You would need to break checkFiles method. Execute the HTTP part in the background thread and pass the parsed responseData to main UI thread. After that update the UI. See if this works...

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top