等待使用NSCondition的异步方法 [英] Waiting on asynchronous methods using NSCondition

查看:86
本文介绍了等待使用NSCondition的异步方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在通过Internet异步下载四个plist文件.我需要等到所有四个文件都下载完之后,才可以在第一次运行时推送UIViewController,或者在以后的所有运行中刷新数据并重新加载所有UITableViews.

I am downloading four plist files asynchronously over the internet. I need to wait until all four files are downloaded, until I either on the first run, push a UIViewController, or on all subsequent runs, refresh the data, and reload all my UITableViews.

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

On the first run, everything works perfectly. When refreshing though, all four url requests are called, and started, but never call their completion or failure blocks, and the UI freezes. Which is odd since I preform all operations in a background thread. I have not been able to figure out why this is happening.

首次加载和刷新方法以相同的方式调用四个更新"方法,并以相同的方式使用NSCondition.

The first load and the refresh methods call the four "update" methods in the same way, and use NSCondition in the same way.

第一次运行:

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

上面的每种加载方法中出现的下面的块,对应于将下载并更新特定数据的方法.

The block below that appears in each loading method above, corresponds to a method that will download and update a specific piece of data.

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

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

and the completion and failure blocks call the same method to update the data

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

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

So, Why does this work on the first run, but not any of the subsequent runs?

推荐答案

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

As I mentioned in my comments, above, the most obvious problem is that you're invoking methods that use condition before you initialize condition. Make sure initialize condition before you start calling updateCompetitionResults, etc.

关于更重大的变化,我建议完全退出NSCondition,并使用操作队列:

In terms of a more radical change, I might suggest retiring NSCondition altogether, and use operation queues:

  1. 我可能会使用 NSOperationQueue (或者,您也可以使用调度组,但是我喜欢操作队列配置您可以操作多少个并发操作的功能……如果您想达到某个目的,也可以使用它.取消操作,我认为NSOperationQueue也提供了一些不错的功能).然后,您可以将每个下载和处理定义为单独的 NSOperation (每个下载都应同步进行,因为它们在操作队列中运行,您可以获得异步操作的好处,但是下载完成后可以立即开始后处理).然后,您只需将它们排队等待异步运行,但是定义一个最终操作,该操作将取决于其他四个,一旦完成四个下载即开始执行. (顺便说一句,我使用 NSBlockOperation NSOperation对象提供块功能,但您可以按照自己的方式进行操作.)

  1. I might use NSOperationQueue (or you can use dispatch groups, too, if you want, but I like the operation queue's ability to configure how many concurrent operations you can operate ... also if you get to a point that you want to cancel operations, I think NSOperationQueue offers some nice features there, too). You can then define each download and processing as a separate NSOperation (each of the downloads should happen synchronously, because they're running in an operation queue, you get the benefits of asynchronous operations, but you can kick off the post-processing immediately after the download is done). You then just queue them up to run asynchronously, but define a final operation which is dependent upon the other four will kick off as soon as the four downloads are done. (By the way, I use NSBlockOperation which provides block-functionality for NSOperation objects, but you can do it any way you want.)

尽管updateProgramContent可能异步下载,但它会依次处理四个下载的文件,一个接一个.因此,如果第一次下载需要一段时间才能下载,它将阻止其他下载的后处理.相反,我希望将四个plist文件中的每个文件的下载和后期处理都封装在单个NSOperation中.因此,我们不仅享受下载和后期处理的最大并发性.

And whereas your updateProgramContent might download asynchronously, it processes the four downloaded files sequentially, one after another. Thus, if the first download takes a while to download, it will hold up the post-processing of the others. Instead, I like to encapsulate both the downloading and the post processing of each of the four plist files in a single NSOperation, each. Thus, we enjoy maximal concurrency of not only the downloading, but the post-processing, too.

我可能会倾向于使用允许您下载plist的NSDictionaryNSArray功能,而不是使用AFNetworking(我通常是pclist的忠实拥护者)从网上下载并加载到适当的结构中.这些dictionaryWithContentsOfURLarrayWithContentsOfURL可以同步运行,但是由于我们是在后台操作中执行此操作,因此所有内容都可以按您希望的方式异步运行.这也绕过了将它们保存到文件的过程.如果您希望将它们保存到Documents目录中的文件中,则也可以轻松地做到这一点.显然,如果您在下载plist文件时正在做一些复杂的事情(例如,您的服务器正在执行某些质询-响应身份验证),则不能使用便捷的NSDictionaryNSArray方法.但是,如果您不需要所有这些内容,则简单的NSDictionaryNSArray方法___WithContentsOfURL使生活变得非常简单.

Rather than using the AFNetworking (which I'm generally a big fan of) plist-related method, I might be inclined to use NSDictionary and NSArray features that allow you to download a plist from the web and load them into the appropriate structure. These dictionaryWithContentsOfURL and arrayWithContentsOfURL run synchronously, but because we're doing this in a background operation, everything runs asynchronously like you want. This also bypasses the saving them to files. If you wanted them saved to files in your Documents directory, you can do that easily, too. Clearly, if you're doing something sophisticated in your downloading of the plist files (e.g. your server is engaging in some challenge-response authentication), you can't use the convenient NSDictionary and NSArray methods. But if you don't need all of that, the simple NSDictionary and NSArray methods, ___WithContentsOfURL make life pretty simple.

将它们全部拉在一起,可能看起来像这样:

Pulling this all together, it might look like:

@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等.

Now, I don't know which of your plist's are arrays and which are dictionaries (in my example, I made competitions an array and the rest were dictionaries keyed by the competition id), but hopefully you get the idea of what I was shooting for. Maximize concurrency, eliminate NSCondition logic, really make the most of NSOperationQueue, etc.

这可能是要接受的全部,但我仅提及它作为NSCondition的替代方法.如果您当前的技术有效,那就太好了.但是上面概述了我将如何应对这样的挑战.

This may be all to much to take in, but I only mention it as an alternative to NSCondition. If your current technique works, that's great. But the above outlines how I would tackle a challenge like this.

这篇关于等待使用NSCondition的异步方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆