Ios 使用NSCondition等待异步方法

Ios 使用NSCondition等待异步方法,ios,multithreading,nsurlrequest,nscondition,Ios,Multithreading,Nsurlrequest,Nscondition,我正在通过internet异步下载四个plist文件。我需要等待所有四个文件下载完毕,直到我在第一次运行时按下UIViewController,或在所有后续运行时刷新数据,然后重新加载所有UITableView 在第一次运行时,一切都很完美。但在刷新时,会调用并启动所有四个url请求,但不会调用它们的完成或失败块,UI会冻结。这很奇怪,因为我在后台线程中执行了所有操作。我一直无法理解为什么会发生这种情况 第一个load和refresh方法以相同的方式调用四个“update”方法,并以相同的方式使

我正在通过internet异步下载四个plist文件。我需要等待所有四个文件下载完毕,直到我在第一次运行时按下UIViewController,或在所有后续运行时刷新数据,然后重新加载所有UITableView

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

第一个load和refresh方法以相同的方式调用四个“update”方法,并以相同的方式使用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;
    }
}
它运行:

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

那么,为什么这在第一次运行时起作用,而在随后的任何运行中都不起作用呢?

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


就更彻底的变化而言,我可能建议完全退出
NSCondition
,并使用操作队列:

  • 我可能会使用(如果您愿意,您也可以使用调度组,但我喜欢操作队列配置您可以操作多少并发操作的功能……此外,如果您想要取消操作,我认为
    NSOperationQueue
    也提供了一些不错的功能)。然后,您可以将每个下载和处理定义为一个单独的过程(每个下载应该同步进行,因为它们在操作队列中运行,您可以从异步操作中获得好处,但您可以在下载完成后立即启动后处理)。然后,您只需将它们排队以异步运行,但定义一个最终操作,该操作依赖于其他四个,并将在四次下载完成后立即启动。(顺便说一句,我使用它为
    NSOperation
    对象提供块功能,但您可以按任何方式进行操作。)

  • 您的
    updateProgramContent
    可能会异步下载,但它会依次处理四个下载的文件。因此,如果第一次下载需要一段时间才能下载,则会阻碍其他下载的后期处理。相反,我喜欢将四个plist文件的下载和后处理都封装在一个
    NSOperation
    中,每个文件都是。因此,我们不仅享受下载的最大并发性,还享受后期处理的最大并发性

  • 我可能倾向于使用
    NSDictionary
    NSArray
    功能,而不是使用
    AFNetworking
    (我通常非常喜欢)plist相关方法,这些功能允许您从web下载plist并将其加载到适当的结构中。这些
    dictionaryWithContentsOfURL
    arrayWithContentsOfURL
    同步运行,但由于我们是在后台操作中执行此操作,因此所有内容都会按照您的要求异步运行。这也绕过了将它们保存到文件的过程。如果您希望将它们保存到
    文档
    目录中的文件中,您也可以轻松地执行此操作。显然,如果您在下载plist文件时做了一些复杂的工作(例如,您的服务器正在进行一些质询-响应身份验证),则不能使用方便的
    NSDictionary
    NSArray
    方法。但是如果你不需要所有这些,简单的
    NSDictionary
    NSArray
    方法,
    \uuuuuuu和url
    内容使生活变得非常简单

  • 把这些放在一起,它可能看起来像:

    @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中哪些是数组,哪些是字典(在我的示例中,我将竞赛设置为数组,其余的是由竞赛id键入的字典),但希望您了解我的拍摄目的。最大化并发性,消除
    NSCondition
    逻辑,真正充分利用
    NSOperationQueue
    ,等等


    这可能有太多需要理解的地方,但我只提到它是作为
    NSCondition
    的一种替代方案。如果你目前的技术有效,那就太好了。但是上面概述了我将如何应对这样的挑战。

    正如我在上面的评论中提到的,最明显的问题是,在初始化
    条件之前,您正在调用使用
    条件的方法。在开始调用
    updateCompetitionResults
    等之前,请确保初始化
    条件


    就更彻底的变化而言,我可能建议完全退出
    NSCondition
    ,并使用操作队列:

  • 我可能会使用(如果您愿意,您也可以使用调度组,但我喜欢操作队列配置您可以操作多少并发操作的功能……此外,如果您想要取消操作,我认为
    NSOperationQueue
    也提供了一些不错的功能)。然后,您可以将每个下载和处理定义为一个单独的过程(每个下载应该同步进行,因为它们在操作队列中运行,您可以从异步操作中获得好处,但您可以在下载完成后立即启动后处理)。然后,您只需将它们排队以异步运行,但定义一个最终操作,该操作依赖于其他四个,并将在四次下载完成后立即启动。(顺便说一句,我使用它为
    NSOperation
    对象提供块功能,但您可以按任何方式进行操作。)

  • 而你的
    updateProgramCo
    
    @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