2012-12-23 182 views
2

我通过互联网异步下载四个plist文件。我需要等到所有四个文件都下载完毕,直到我第一次运行时,推送一个UIViewController或所有后续运行时,刷新数据并重新加载所有UITableView。等待使用NSCondition的异步方法

第一次运行时,一切正常。刷新时,所有四个url请求都被调用并启动,但从不调用它们的完成或失败块,并且UI冻结。这是奇怪的,因为我在后台线程中执行所有操作。我一直无法弄清楚为什么会发生这种情况。

第一次加载和刷新方法以相同的方式调用四个“更新”方法,并以相同的方式使用NSCondition。

对于第一运行:

- (void)loadContentForProgram:(NSString *)programPath 
{ 
    NSLog(@"Start Load Program"); 
    AppDelegate *myDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate; 
    hud = [[MBProgressHUD alloc] initWithView:myDelegate.window]; 
    [myDelegate.window addSubview:hud]; 
    hud.labelText = @"Loading..."; 
    hud.detailsLabelText = @"Loading Data"; 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ 
     //Do stuff here to load data from files 

     //Update From online files 
     hud.detailsLabelText = @"Updating Live Data"; 
     resultLock = NO; 
     progressLock = NO; 
     recallLock = NO; 
     stageLock = NO; 

     condition = [[NSCondition alloc] init]; 
     [condition lock]; 

     [self updateCurrentCompsText]; 
     [self updateCompetitionResults]; 
     [self updateCompetitionRecalls]; 
     [self updateCompetitionProgress]; 


     while (!resultLock) { 
      [condition wait]; 
     } 
     NSLog(@"Unlock"); 
     while (!stageLock) { 
      [condition wait]; 
     } 
     NSLog(@"Unlock"); 
     while (!recallLock) { 
      [condition wait]; 
     } 
     NSLog(@"Unlock"); 
     while (!progressLock) { 
      [condition wait]; 
     } 
     NSLog(@"Unlock"); 
     [condition unlock]; 
     updateInProgress = NO; 
     //Reset Refresh controls and table views 
     self.refreshControlsArray = [[NSMutableArray alloc] init]; 
     self.tableViewsArray = [[NSMutableArray alloc] init]; 
     NSLog(@"Finished Loading Program"); 
     [[NSNotificationCenter defaultCenter] postNotificationName:@"WMSOFinishedLoadingProgramData" object:nil]; //Pushes view controller 
     dispatch_async(dispatch_get_main_queue(), ^{ 
      [MBProgressHUD hideHUDForView:myDelegate.window animated:YES]; 
     }); 
    }); 
} 

刷新当数据:

- (void)updateProgramContent 
{ 
    if (!updateInProgress) { 
     updateInProgress = YES; 
     for (int i = 0; i < self.refreshControlsArray.count; i++) { 
      if (!((UIRefreshControl *)self.refreshControlsArray[i]).refreshing) { 
       [self.refreshControlsArray[i] beginRefreshing]; 
       [self.tableViewsArray[i] setContentOffset:CGPointMake(0.0, 0.0) animated:YES]; 
      } 
     } 

     resultLock = NO; 
     stageLock = NO; 
     recallLock = NO; 
     progressLock = NO; 
     dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ 

      condition = [[NSCondition alloc] init]; 
      [condition lock]; 

      [self updateCompetitionProgress]; 
      [self updateCompetitionRecalls]; 
      [self updateCompetitionResults]; 
      [self updateCurrentCompsText]; 

      while (!resultLock) { 
       [condition wait]; 
      } 
      NSLog(@"Unlock"); 
      while (!stageLock) { 
       [condition wait]; 
      } 
      NSLog(@"Unlock"); 
      while (!recallLock) { 
       [condition wait]; 
      } 
      NSLog(@"Unlock"); 
      while (!progressLock) { 
       [condition wait]; 
      } 
      NSLog(@"Unlock"); 
      [condition unlock]; 
     }); 

     for (int i = 0; i < self.refreshControlsArray.count; i++) { 
      [self.refreshControlsArray[i] performSelector:@selector(endRefreshing) withObject:nil afterDelay:1.0]; 
      [self.tableViewsArray[i] performSelector:@selector(reloadData) withObject:nil afterDelay:1.0]; 
     } 
     updateInProgress = NO; 
    } 
} 

低于该块出现在每个加载方法的上方,对应于将下载和更新特定片的方法数据的。

[self updateCompetitionProgress]; 
[self updateCompetitionRecalls]; 
[self updateCompetitionResults]; 
[self updateCurrentCompsText]; 

它运行:

- (void)updateCompetitionResults 
{ 
    __block NSDictionary *competitionResultsData = nil; 
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"Some URL",[self.programName stringByReplacingOccurrencesOfString:@" " withString:@"%20"]]] cachePolicy:NSURLCacheStorageNotAllowed timeoutInterval:20.0]; 
    AFPropertyListRequestOperation *operation = [AFPropertyListRequestOperation propertyListRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id propertyList) { 
     competitionResultsData = (NSDictionary *)propertyList; 
     [competitionResultsData writeToFile:[@"SOME LOCAL PATH"] atomically:NO]; 
     [self updateCompetitionResultsWithDictionary:competitionResultsData]; 
    } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id propertyList) { 
     competitionResultsData = [NSDictionary dictionaryWithContentsOfFile:[@"SOME LOCAL PATH"]]; 
     NSLog(@"Failed to retreive competition results: %@", error); 
     [self updateCompetitionResultsWithDictionary:competitionResultsData]; 
    }]; 
    [operation start]; 
} 

,并完成与失败块调用相同的方法来更新数据

- (void)updateCompetitionResultsWithDictionary:(NSDictionary *)competitionResultsData 
{ 
    //Do Stuff with the data here 
    resultLock = YES; 
    [condition signal]; 
} 

那么,为什么在第一次运行这个工作,但没有任何后续运行?

+0

如果没有别的,我建议在调用你的四个更新方法(例如'updateCompetitionResults')之前初始化你的'condition'。鉴于'updateCompetitionResults'正在调用'updateCompetitionResultsWithDictionary',它使用'condition',所以在调用'updateCompetitionResults'之前,你应该初始化'condition'。但问题可以在其他地方休息。顺便说一下,这是一种非常混乱的方式来实现你想要的,但我认为你已经有了你的理由。 – Rob

+0

好吧,我改变了NSCondition的初始化位置,你是对的,它应该在更新方法之前被调用。你称之为混淆,是否有更好的方法来下载四个plist文件,使用这些信息,然后在完成所有工作之后做一些事情? –

回答

4

正如我在上面的评论中提到的,最明显的问题是在初始化condition之前调用的方法使用condition。确保初始化condition你开始打电话updateCompetitionResults前等


在一个更激进的变化方面,我可能会建议干脆退休NSCondition,并使用操作队列:

  1. 我可能会使用NSOperationQueue (或者你也可以使用派遣组,如果你愿意的话,但是我喜欢操作队列配置你可以操作多少个并发操作的能力......如果你想要取消操作,我认为NSOperationQueue也提供了一些不错的功能)。然后,您可以将每个下载和处理定义为一个单独的NSOperation(每个下载应该同步发生,因为它们正在运行队列中运行,您可以获得异步操作的好处,但是您可以立即启动后处理下载完成)。然后您只需将它们排队以异步运行,但定义最终操作取决于其他四个将在四次下载完成后立即启动。 (顺便说一下,我用NSBlockOperation这对于NSOperation对象提供了块的功能,但你可以做任何你想要的方式。)

  2. 又鉴于您updateProgramContent可能异步下载,它按顺序处理下载​​的四个文件,一个之后。因此,如果第一次下载需要一段时间下载,它将阻止其他人的后期处理。相反,我喜欢将每个plist文件的下载和后处理封装在一个单独的NSOperation中。因此,我们不仅享受下载的最大并发性,还享受后处理的最大并发性。

  3. 而不是使用AFNetworking(这通常我的大风扇)的plist相关的方法,我可能会倾向于使用NSDictionaryNSArray功能,让您从网络上下载的plist并将它们加载到适当的结构。这些dictionaryWithContentsOfURLarrayWithContentsOfURL是同步运行的,但是因为我们是在后台操作中执行此操作,所以所有内容都按照您想要的方式异步运行。这也绕过了将它们保存到文件。如果你想将它们保存到你的Documents目录中的文件中,你也可以轻松地做到这一点。显然,如果您在下载plist文件时做了一些复杂的工作(例如您的服务器正在进行一些质询 - 响应认证),则无法使用方便的NSDictionaryNSArray方法。但如果你不需要所有这些,简单的NSDictionaryNSArray方法,___WithContentsOfURL使生活变得非常简单。

拉这一切在一起,它可能看起来像:

@interface ViewController() 

@property (nonatomic, strong) NSArray *competitions; 
@property (nonatomic, strong) NSDictionary *competitionResults; 
@property (nonatomic, strong) NSDictionary *competitionRecalls; 
@property (nonatomic, strong) NSDictionary *competitionProgress; 

@end 

@implementation ViewController 

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    [self transfer]; 
} 

- (void)allTransfersComplete 
{ 
    BOOL success; 

    if (self.competitions == nil) 
    { 
     success = FALSE; 
     NSLog(@"Unable to download competitions"); 
    } 

    if (self.competitionResults == nil) 
    { 
     success = FALSE; 
     NSLog(@"Unable to download results"); 
    } 

    if (self.competitionRecalls == nil) 
    { 
     success = FALSE; 
     NSLog(@"Unable to download recalls"); 
    } 

    if (self.competitionProgress == nil) 
    { 
     success = FALSE; 
     NSLog(@"Unable to download progress"); 
    } 

    if (success) 
    { 
     NSLog(@"all done successfully"); 
    } 
    else 
    { 
     NSLog(@"one or more failed"); 
    } 
} 

- (void)transfer 
{ 
    NSURL *baseUrl = [NSURL URLWithString:@"http://insert.your.base.url.here/competitions"]; 
    NSURL *competitionsUrl = [baseUrl URLByAppendingPathComponent:@"competitions.plist"]; 
    NSURL *competitionResultsUrl = [baseUrl URLByAppendingPathComponent:@"competitionresults.plist"]; 
    NSURL *competitionRecallsUrl = [baseUrl URLByAppendingPathComponent:@"competitionrecalls.plist"]; 
    NSURL *competitionProgressUrl = [baseUrl URLByAppendingPathComponent:@"competitionprogress.plist"]; 

    NSOperationQueue *queue = [[NSOperationQueue alloc] init]; 
    queue.maxConcurrentOperationCount = 4; // if your server doesn't like four concurrent requests, you can ratchet this back to whatever you want 

    // create operation that will be called when we're all done 

    NSBlockOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{ 

     // any stuff that can be done in background should be done here 

     [[NSOperationQueue mainQueue] addOperationWithBlock:^{ 

      // any user interface stuff should be done here; I've just put this in a separate method so this method doesn't get too unwieldy 

      [self allTransfersComplete]; 
     }]; 
    }]; 

    // a variable that we'll use as we create our four download/process operations 

    NSBlockOperation *operation; 

    // create competitions operation 

    operation = [NSBlockOperation blockOperationWithBlock:^{ 

     // download the competitions and load it into the ivar 
     // 
     // note, if you *really* want to download this to a file, you can 
     // do that when the download is done 

     self.competitions = [NSArray arrayWithContentsOfURL:competitionsUrl]; 

     // if you wanted to do any post-processing of the download 
     // you could do it here.    
     NSLog(@"competitions = %@", self.competitions); 
    }]; 
    [completionOperation addDependency:operation]; 

    // create results operation 

    operation = [NSBlockOperation blockOperationWithBlock:^{ 

     self.competitionResults = [NSDictionary dictionaryWithContentsOfURL:competitionResultsUrl]; 

     NSLog(@"competitionResults = %@", self.competitionResults); 
    }]; 
    [completionOperation addDependency:operation]; 

    // create recalls operation 

    operation = [NSBlockOperation blockOperationWithBlock:^{ 

     self.competitionRecalls = [NSDictionary dictionaryWithContentsOfURL:competitionRecallsUrl]; 

     NSLog(@"competitionRecalls = %@", self.competitionRecalls); 
    }]; 
    [completionOperation addDependency:operation]; 

    // create progress operation 

    operation = [NSBlockOperation blockOperationWithBlock:^{ 

     self.competitionProgress = [NSDictionary dictionaryWithContentsOfURL:competitionProgressUrl]; 

     NSLog(@"competitionProgress = %@", self.competitionProgress); 
    }]; 
    [completionOperation addDependency:operation]; 

    // queue the completion operation (which is dependent upon the other four) 

    [queue addOperation:completionOperation]; 

    // now queue the four download and processing operations 

    [queue addOperations:completionOperation.dependencies waitUntilFinished:NO]; 
} 

@end 

现在,我不知道,你的plist中的的是数组,哪些是字典(在我的例子,我做了比赛的阵列和其余的是由竞争对手编号键入的字典),但希望你能了解我所拍摄的内容。最大化并发性,消除NSCondition逻辑,真正使大部分NSOperationQueue

这可能是大家多参加,但我只提到它作为替代NSCondition。如果你目前的技术有效,那很好。但是,上面概述了我将如何应对这样的挑战。

+0

这完美的作品!谢谢!为NSOperation开沟NSCondition非常有意义,我将我写的代码量减少了一半。它还修复了我在尝试多次运行代码以更新时发生的错误,并且AFNetworking请求无法启动。尽管我做了一些细微的改动,将' - (void)transfer'改为' - (void)transfer:(BOOL)wait'(并且在最后一行使用wait)。这样我就可以通过传递YES来确保我需要的所有内容都被加载并存在于第一次运行中,并且在更新时通过传递NO来在后台运行它。 –