【问题标题】:iphone nsoperation application freezesiphone nsoperation 应用程序冻结
【发布时间】:2013-12-07 06:21:10
【问题描述】:

我制作了NSOperation 的一个子类,叫做 ˚ 来实现多电影下载。在 appDelegate.m 中,我制作了 NSOperationQueue 的对象。

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

MovieDownloadOperation 依赖于一个名为 Downloader 的类,它实际上会下载电影 并给出回调movieCompletelyDownloadedWithUrl: .
然后,我在MovieDownloadOperation 中创建了一个名为downloadState 的属性。它具有不同的值,例如 "STARTED""DOWNLOADING""COMPLETED""ERROR"

MyDownloadOperation 看起来像

-(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" ;
}

这适用于一部电影,但当我尝试下载多部电影时,UI 会冻结,直到下载第一部电影。我认为问题在于main 方法中的while 循环,有没有更好的方法来检查_downloadState 是否更改为"COMPLETED"??

【问题讨论】:

    标签: ios iphone nsoperation nsoperationqueue


    【解决方案1】:

    目前尚不清楚为什么 UI 会因多次操作而冻结,但不是仅一次下载。但是,您的代码示例引发了一些想法:

    1. 并发操作:

      而不是在main 中使用while 循环,您通常会将您的操作定义为并发的(即从isConcurrent 返回YES)。然后movieCompletelyDownloadedWithUrl 将发布isFinished 事件,这将触发操作完成。

      关于如何进行并发操作,你可以定义executingfinished的属性:

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

      您可能希望为 URL 和下载器设置一个 strong 属性:

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

      然后你可能在操作子类中有如下代码:

      @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"];
      }
      

      因此,使用这些方法,您可能会像上面一样让movieCompletelyDownloadedWithUrl 调用completeOperation,这将确保发布isExecutingisFinished 通知。您还需要响应取消事件,如果操作被取消,请确保取消下载。

      有关详细信息,请参阅并发编程指南Configuring Operations for Concurrent Execution部分。

    2. 直到main:

      才开始下载

      我没有看到您的 main 方法启动下载。这让我很紧张,您的Downloader 初始化方法initWithURL 可能正在启动下载,这很糟糕。您不希望在创建操作时启动下载,而是在操作开始之前不应该这样做(例如startmain)。所以,在我上面的例子中,我只有 initWithURL 保存 URL,然后 main 是开始下载的。

    3. NSOperation 中使用NSURLConnectionDataDelegate 方法:

      顺便说一句,您没有分享您的操作是如何处理网络请求的。如果您使用NSURLConnectionDataDelegate 方法,当您摆脱main 中的while 循环时,如果您不将NSURLConnection 安排在特定的运行循环中,您可能会遇到问题。例如,您可能会这样做:

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

      如果您没有使用 NSURLConnectionDataDelegate 方法,或者如果您已经解决了这个运行循环问题,那么请忽略这个建议,但是,最重要的是,当您在操作中修复 main 方法时,您可能揭露您的旧 main 可能对您隐藏的 NSURLConnection 问题。

    4. Downloader 如何调用moveCompleteDownloadedWithUrl

      顺便说一句,您没有展示Downloader 可能如何调用moveCompleteDownloadedWithUrl。这看起来很可疑,但我只是希望您在发布代码时简化了代码。但是,如果您不使用协议委托模式或完成块模式,那么我会非常担心您的多个 Downloader 对象如何通知相应的 MyDownloadOperation 对象下载已完成。就个人而言,我可能倾向于将这两个不同的类重构为一个,但这是个人喜好问题。

    【讨论】:

      【解决方案2】:

      您可以使用NSTimer 来检查您的下载是否完成。它不会冻结您的用户界面

      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];
           }
       }
      

      【讨论】:

        猜你喜欢
        • 2011-08-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-04
        • 2015-05-09
        • 2013-09-26
        • 2014-09-29
        • 2016-02-10
        相关资源
        最近更新 更多