Subbu
Subbu

Reputation: 2101

iphone nsoperation application freezes

I have made a subclass of NSOperation called ˚ to achieve multiple movie downloads . In the appDelegate.m , I have made an object of NSOperationQueue .

- (void)applicationDidFinishLaunching:(UIApplication *)application {
   queue = [[NSOperationQueue alloc] init];
   [queue setMaximumConcurrentOperationCount:5]
} 

MovieDownloadOperation depends on a class called Downloader which actually downloads the movie and gives callback movieCompletelyDownloadedWithUrl: .
Then , I have made a property called downloadState in MovieDownloadOperation . It has different values like "STARTED" , "DOWNLOADING" , "COMPLETED" , "ERROR".

MyDownloadOperation looks like

-(id)initWithUrl:(NSURL *)url
{

   if (self = [super init])
   {
      _downloader = [[Downloader alloc] initWithUrl:url];
      _downloadState = @"STARTED" ;
   }
}

-(void)main
{
    while(1)
    {
       if ([_downloadState isEqualToString:@"COMPLETED"])
         {
              NSLog(@"movie downloaded successfully");
              break ;
          }
    }

}

-(void)movieCompletelyDownloadedWithUrl:(NSURL *)url
{
    _downloadState = @"COMPLETED" ;
}

This works well for one movie , but when I try to download more than one movie , the UI freezes until the first is downloaded . I think the the problem is the while loop inside the main method , is there a better way to check if the _downloadState is changed to "COMPLETED" ??

Upvotes: 0

Views: 353

Answers (2)

Rob
Rob

Reputation: 438437

It's unclear why the UI freezes with multiple operations, but not with only one download. But, your code sample provokes a couple of thoughts:

  1. Concurrent Operation:

    Rather than having a while loop in main, and you'd generally would define your operation to be concurrent (i.e. return YES from isConcurrent). Then movieCompletelyDownloadedWithUrl would post the isFinished event, which would trigger the completion of the operation.

    In terms of how to make a concurrent operation, you might define properties for executing and finished:

    @property (nonatomic, readwrite, getter = isFinished)  BOOL finished;
    @property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
    

    You'd probably want to have a strong property for the URL and the downloader:

    @property (nonatomic, strong)               NSURL *url;
    @property (nonatomic, strong)               Downloader *downloader;
    

    And then you might have the following code in the operation subclass:

    @synthesize finished  = _finished;
    @synthesize executing = _executing;
    
    - (id)init
    {
        self = [super init];
    
        if (self) {
            _finished  = NO;
            _executing = NO;
        }
    
        return self;
    }
    
    - (id)initWithUrl:(NSURL *)url
    {
        self = [self init];
    
        if (self) {
            // Note, do not start downloader here, but just save URL so that
            // when the operation starts, you have access to the URL.
    
            _url = url;
        }
    
        return self;
    }
    
    - (void)start
    {
        if ([self isCancelled]) {
            self.finished = YES;
            return;
        }
    
        self.executing = YES;
    
        [self main];
    }
    
    - (void)main
    {
        // start the download here
    
        self.downloader = [[Downloader alloc] initWithUrl:self.url];
    }
    
    - (void)completeOperation
    {
        self.executing = NO;
        self.finished  = YES;
    }
    
    // you haven't shown how this is called, but I'm assuming you'll fix the downloader
    // to call this instance method when it's done
    
    - (void)movieCompletelyDownloadedWithUrl:(NSURL *)url
    {
        [self completeOperation];
    }
    
    #pragma mark - NSOperation methods
    
    - (BOOL)isConcurrent
    {
        return YES;
    }
    
    - (void)setExecuting:(BOOL)executing
    {
        [self willChangeValueForKey:@"isExecuting"];
        _executing = executing;
        [self didChangeValueForKey:@"isExecuting"];
    }
    
    - (void)setFinished:(BOOL)finished
    {
        [self willChangeValueForKey:@"isFinished"];
        _finished = finished;
        [self didChangeValueForKey:@"isFinished"];
    }
    

    So, with these methods, you might then have movieCompletelyDownloadedWithUrl call completeOperation like above, which will ensure that isExecuting and isFinished notifications get posted. You'd also want to respond to cancellation event, too, making sure to cancel the download if the operation is canceled.

    See Configuring Operations for Concurrent Execution section of the Concurrency Programming Guide for more details.

  2. Don't initiate download until main:

    I don't see your main method initiating the download. That makes me nervous that your Downloader initialization method, initWithURL, might be initiating the download, which would be bad. You don't want downloads initiating when you create the operation, but rather you shouldn't do that until the operation starts (e.g. start or main). So, in my above example, I only have initWithURL save the URL, and then main is what starts the download.

  3. Using NSURLConnectionDataDelegate methods in NSOperation:

    As an aside, you didn't share how your operation is doing the network request. If you're using NSURLConnectionDataDelegate methods, when you get rid of that while loop in main, you might have problems if you don't schedule the NSURLConnection in a particular run loop. For example, you might do:

    NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
    [connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
    [connection start];
    

    If you're not using NSURLConnectionDataDelegate methods, or if you've already addressed this run loop issue, then ignore this counsel, but, bottom line, when you fix the main method in your operation, you might expose the NSURLConnection issue that your old main might have hidden from you.

  4. How does Downloader invoke moveCompleteDownloadedWithUrl?

    BTW, you're not showing how Downloader could possibly invoke moveCompleteDownloadedWithUrl. That looks suspicious, but I'm just hoping you simplified your code when you posted it. But if you're not using a protocol-delegate pattern or completion block pattern, then I'd be very nervous about how your multiple Downloader objects are informing the respective MyDownloadOperation objects that the download is done. Personally, I might be inclined to refactor these two differ classes into one, but that's a matter of personal taste.

Upvotes: 2

Tapas Pal
Tapas Pal

Reputation: 7207

You can use NSTimer to check whether your download is completed or not. It'll not freezes your UI

NSTimer *localTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(checkDownloadStatus) userInfo:nil repeats:YES]; 

-(void)checkDownloadStatus
 {
     if ([_downloadState isEqualToString:@"COMPLETED"])
     {
          NSLog(@"movie downloaded successfully");
          [localTimer invalidate];
     }
 }

Upvotes: 0

Related Questions