等待使用NSCondition的异步方法

时间:2012-12-23 19:16:59

标签: ios multithreading nsurlrequest nscondition

我正在互联网上异步下载四个plist文件。我需要等到所有四个文件都被下载,直到我第一次运行,推送UIViewController,或者在所有后续运行中,刷新数据,然后重新加载我所有的UITableViews。

在第一轮比赛中,一切都运转良好。刷新时,所有四个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;
    }
}

上面每个加载方法中出现的下面的块对应于将下载和更新特定数据的方法。

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

那么,为什么这会在第一次运行时起作用,而不是后续的任何运行?

1 个答案:

答案 0 :(得分:4)

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


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

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

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

  3. 我可能倾向于使用AFNetworkingNSDictionary功能,而不是使用NSArray(我通常是它的忠实粉丝)与plist相关的方法允许您从Web下载plist并将其加载到适当的结构中。这些dictionaryWithContentsOfURLarrayWithContentsOfURL同步运行,但由于我们在后台操作中执行此操作,因此所有内容都会以您想要的方式异步运行。这也绕过了将它们保存到文件中。如果您希望将它们保存到Documents目录中的文件中,您也可以轻松完成。显然,如果您在下载plist文件时做了一些复杂的事情(例如,您的服务器正在进行一些质询 - 响应身份验证),则无法使用方便的NSDictionaryNSArray方法。但如果你不需要所有这些,简单的NSDictionaryNSArray方法,___WithContentsOfURL会让生活变得非常简单。

  4. 将它们全部拉到一起,它可能看起来像:

    @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的替代方案。如果您当前的技术有效,那就太好了。但是上面概述了我将如何应对这样的挑战。