User User - 22 days ago 12
iOS Question

Objective-C: Fading GUI

I want to download the zip and unzip it. When I downloaded the zip, everything works fine. But when unzip my interface stops for a few seconds and sometimes app crashes. How to fix it?

Zip File size 650 MB

downloadButton - activates downloading on button click

-(IBAction) downloadButton:(id)sender{

_url1 =[NSURL URLWithString:@"link"];

_downloadTask1 = [_session downloadTaskWithURL:_url1];

[_downloadTask1 resume];
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{

if (downloadTask == _downloadTask1) {

_paths1 = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
_documentsDirectory1 = [_paths1 objectAtIndex:0];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *newLocation = [NSURL URLWithString:[NSString stringWithFormat:@"file://%@/1.zip", _documentsDirectory1]];
NSError *error;
[fileManager copyItemAtURL:location toURL:newLocation error:&error];
}
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;{

if( downloadTask == _downloadTask1){
_progress1 = [[NSNumber numberWithInteger:totalBytesWritten] floatValue];_total1 = [[NSNumber numberWithInteger:totalBytesExpectedToWrite] floatValue];
_percentage = [NSString stringWithFormat:@"%.f%%", ((_progress1 / _total1) * 100)];
(NSLog (_percentage, @"%.f%%"));
_label.text = _percentage;
}
if ([_percentage isEqual: @"100%"]) {
_label.text = @«ok»;
[self performSelector:@selector(zip) withObject:nil afterDelay:0.1];
[self performSelector:@selector(removeZip) withObject:nil afterDelay:5.0];
}
}

-(void) zip{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
_paths1 = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
_documentsDirectory1 = [_paths1 objectAtIndex:0];
_zipPath1 = [_documentsDirectory1 stringByAppendingPathComponent:@"1.zip"];
_destinationPath1 = [NSString stringWithFormat:@"%@",_documentsDirectory1];
[SSZipArchive unzipFileAtPath:_zipPath1 toDestination:_destinationPath1];
});
}

-(void) removeZip{
_paths1 = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
_documentsDirectory1 = [_paths1 objectAtIndex:0];
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager removeItemAtPath:[_documentsDirectory1 stringByAppendingPathComponent:@"1.zip"] error:nil];
}

Answer

Your handling of threads/queues is wrong.

The NSURLSession delegate methods are called on an NSOperationQueue, which is a background queue unless you specifically set it up to use a queue that runs on the main thread.

Thus you can do time-consuming things in the delegate methods without blocking the UI.

However, any code that updates the UI needs to be performed on the main thread.

You can get rid of the dispatch_async(dispatch_get_global_queue wrapper around your unzip code, since unless you take special steps, that code will be called from a background thread anyway.

However, your code in your session delegate methods that updates labels and makes other UIKit calls needs to performed on the main thread:

- (void)URLSession:(NSURLSession *)session 
        downloadTask: (NSURLSessionDownloadTask *) downloadTask 
        didWriteData: (int64_t) bytesWritten  
        totalBytesWritten: (int64_t) totalBytesWritten
        totalBytesExpectedToWrite:(int64_t) totalBytesExpectedToWrite; {
    if (downloadTask == _downloadTask1) {
        self.progress1 = (float) totalBytesWritten;
        self.total1 =  (float) totalBytesExpectedToWrite;
        self.percentage = [NSString stringWithFormat:@"%.f%%", 
            ((_progress1 / _total1) * 100)];
        NSLog(@"%.f%%", self.percentage, );

        //Setting a label's text is a UI call so it needs to be done on the main thread
        dispatch_async(dispatch_get_main_queue(), ^{
            _label.text = _percentage;
        };
    }
    if ([_percentage isEqual: @"100%"]) {
        _label.text = @«ok»;
        [self performSelector:@selector(zip) withObject:nil afterDelay:0.1];
        [self performSelector:@selector(removeZip) withObject:nil afterDelay:5.0];
    }
}

BTW, this line is needlessly complex and inefficient:

_progress1 = [[NSNumber numberWithInteger:totalBytesWritten] floatValue];

There's no reason to create an NSNumber (an object that requires a memory allocation). Just cast totalBytesWritten directly to a float:

self.progress1 = (float) totalBytesWritten;

And, since that code runs on a background thread, you should make those properties atomic and use the syntax "self.property" to use the property's getters and setters.