2012-12-23 5 views
2

인터넷을 통해 4 개의 plist 파일을 비동기식으로 다운로드합니다. 첫 번째 실행, UIViewController 밀어 또는 모든 후속 실행, 데이터 새로 고침 및 모든 내 UITableViews 다시로드 할 때까지 네 개의 파일을 모두 다운로드 할 때까지 기다려야합니다.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'가'condition'을 사용하는'updateCompetitionResultsWithDictionary'를 호출하면'updateCompetitionResults'를 호출하기 전에'condition'을 초기화해야합니다. 문제는 다른 곳에서도 해결할 수 있습니다. 제쳐두고, 이것은 당신이 원하는 것을 성취하는 매우 혼란스러운 방법이지만, 당신이 당신의 이유를 가지고 있다고 가정합니다. – Rob

+0

음, NSCondition이 초기화 된 위치를 바꿨습니다. 맞습니다. 업데이트 메서드 앞에 호출되어야합니다. 당신은 이것을 혼란스럽게 만들었습니다. 4 개의 plist 파일을 다운로드하고, 정보를 사용하고, 모든 작업이 끝난 후 작업을 수행하는 더 좋은 방법이 있습니까? –

답변

4

위에서 언급 한 것처럼 가장 분명한 문제는 condition을 초기화하기 전에 condition을 사용하는 메서드를 호출한다는 것입니다.

더 급진적 인 변화의 측면에서

, 나는 모두 NSCondition를 은퇴하는 것이 좋습니다 수있는 등, 당신이 updateCompetitionResults를 호출 시작하기 전에 condition를 초기화 확인하고 작업 대기열 사용

  1. 나는 NSOperationQueue을 사용할 수를 (또는 원하는 경우 디스패치 그룹을 사용할 수도 있지만 작업 대기열의 동시 작업 수를 구성 할 수있는 기능이 마음에 든다 ... 작업을 취소하려는 시점에 이르면 NSOperationQueue 거기에 멋진 기능을 제공합니다). 그런 다음 각 다운로드와 처리를 별도의 NSOperation으로 정의 할 수 있습니다 (다운로드가 각각 작업 대기열에서 실행 중이므로 비동기 작업의 이점을 얻을 수 있기 때문에 다운로드가 동 기적으로 수행되어야하지만 즉시 다운로드가 완료됩니다.) 그런 다음 비동기 적으로 실행되도록 대기열에 올려 놓고 네 개의 다운로드가 완료되면 곧 다른 네 개의 작업에 종속적 인 최종 작업을 정의합니다. (그런데, 나는 NSOperation 개체에 대한 블록 기능을 제공 NSBlockOperation 사용하지만, 당신은 당신이 원하는 방식으로 작업을 수행 할 수 있습니다.)

  2. 그리고 반면

    updateProgramContent 비동기 적으로 다운로드 할 수도 있습니다, 그것은 순차적으로, 하나 네 다운로드 한 파일을 처리 또 다른 후. 따라서 첫 번째 다운로드가 다운로드하는 데 시간이 오래 걸리면 다른 사람들의 후 처리를 보류하게됩니다.대신, 나는 다운로드와 두 개의 plist 파일 각각의 후 처리를 단일 NSOperation에 캡슐화하고 싶습니다. 따라서 우리는 다운로드뿐만 아니라 사후 처리의 동시성을 극대화합니다.

  3. 오히려 (I 일반적으로의 큰 팬이에요) AFNetworking PLIST 관련 방법을 사용하는 것보다, 당신이 웹에서 PLIST를 다운로드에로드 할 수 NSDictionaryNSArray 기능을 사용하는 경향 수 있습니다 적절한 구조. 이 dictionaryWithContentsOfURLarrayWithContentsOfURL은 동기식으로 실행되지만 백그라운드 작업에서이 작업을 수행하기 때문에 모든 것이 비동기 적으로 실행됩니다. 또한 파일에 저장하는 것을 우회합니다. 파일을 Documents 디렉토리에 저장하고 싶다면 쉽게 할 수 있습니다. 분명히 plist 파일 다운로드에 정교한 작업 (예 : 서버가 일부 챌린지 - 응답 인증)을 수행하는 경우 편리한 NSDictionaryNSArray 방법을 사용할 수 없습니다. 그러나 모든 것을 필요로하지 않는다면 간단한 NSDictionaryNSArray 방법 인 ___WithContentsOfURL을 사용하면 매우 간단합니다.

모두 함께이 이겠지, 그것과 같을 수 있습니다 나는 이제 배열 인 당신의 PLIST의 알하지 않고 사전 내 예제에서 (있는 지금

@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 

, 나는 대회했다 배열과 나머지는 경쟁 ID에 의해 열쇠가있는 사전이었다). 그러나 잘 만하면 당신은 내가 쏘고 있었던 것의 생각을 얻는다. 동시성을 최대화 NSCondition 로직을 제거, 정말 등

이에 걸릴 정도에 대한 모든 수 있습니다을 NSOperationQueue을 최대한 활용할하지만 난 단지 NSCondition의 대안으로 그것을 언급. 현재의 기술이 효과가 있다면 훌륭합니다. 그러나 위의 내용은 어떻게 내가 이와 같은 도전에 착수 할 것인지를 개략적으로 설명합니다.

+0

이것은 완벽하게 작동합니다! 고맙습니다! NSOperation에 대한 NSCondition을 제거하는 것은 많은 의미가 있습니다. 필자가 작성한 코드의 양을 절반으로 줄였습니다. 또한 코드를 여러 번 실행하여 AFNetworking 요청을 시작하지 않은 오류를 해결합니다. 나는 '- (void) transfer'를'- (void) transfer : (BOOL) wait' (매우 마지막 줄에서 wait를 사용)로 바꾼다. 이 방법은 내가 필요한 모든 것을로드 할 수 있는지 확인하고 YES를 전달하여 첫 번째 실행시 존재하며 업데이트 할 때 NO를 전달하여 백그라운드에서 실행합니다. –

관련 문제