2

インターネット経由で非同期に 4 つの plist ファイルをダウンロードしています。最初の実行で UIViewController をプッシュするか、その後のすべての実行でデータを更新してすべての UITableView をリロードするまで、4 つのファイルすべてがダウンロードされるまで待つ必要があります。

最初の実行では、すべてが完全に機能します。ただし、更新すると、4 つの URL 要求がすべて呼び出されて開始されますが、完了ブロックまたは失敗ブロックは呼び出されず、UI がフリーズします。バックグラウンドスレッドですべての操作を実行するので、これは奇妙です。なぜこれが起こっているのか理解できませんでした。

最初の load メソッドと refresh メソッドは、4 つの「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];
}

では、これが最初の実行では機能するのに、その後の実行では機能しないのはなぜですか?

4

1 に答える 1

5

上記のコメントで述べたように、最も明白な問題は、condition初期化する前に使用するメソッドを呼び出していることですcondition。などのcondition呼び出しを開始する前に、必ず初期化してください。updateCompetitionResults


より抜本的な変更という点では、NSCondition完全に廃止してオペレーション キューを使用することをお勧めします。

  1. 私は使用するかもしれませんNSOperationQueue(または、必要に応じてディスパッチ グループを使用することもできますが、操作できる同時操作の数を構成する操作キューの機能が気に入っています...また、操作をキャンセルしたい場合は、そこにもいくつかの優れた機能があると思いNSOperationQueueます)。次に、各ダウンロードと処理を個別に定義できますNSOperation(各ダウンロードは同期的に発生する必要があります。操作キューで実行されているため、非同期操作の利点が得られますが、ダウンロードの直後に後処理を開始できます)。ダウンロード完了)。次に、非同期に実行するためにそれらをキューに入れるだけですが、他の 4 つに依存する最終操作を定義すると、4 つのダウンロードが完了するとすぐに開始されます。(ちなみに使ってますNSBlockOperationこれはオブジェクトのブロック機能を提供しNSOperationますが、好きなように行うことができます。)

  2. 非同期でダウンロードする場合がありますupdateProgramContentが、ダウンロードした 4 つのファイルを 1 つずつ順番に処理します。したがって、最初のダウンロードのダウンロードに時間がかかると、他のダウンロードの後処理が遅れます。代わりに、4 つの plist ファイルのそれぞれのダウンロードと後処理の両方を 1 つの , それぞれにカプセル化するのが好きですNSOperation。したがって、ダウンロードだけでなく、後処理も最大の同時実行性を享受できます。

  3. AFNetworking(私は一般的に大ファンです) plist 関連の方法を使用するのではなく、Web から plist をダウンロードして適切な構造にロードできる機能を使用する傾向があるかもしれNSDictionaryませんNSArray。これらは同期的dictionaryWithContentsOfURLarrayWithContentsOfURL実行されますが、バックグラウンド操作でこれを行っているため、すべてが必要に応じて非同期で実行されます。これにより、ファイルへの保存もバイパスされます。それらをディレクトリ内のファイルに保存したい場合はDocuments、それも簡単に行うことができます。明らかに、plist ファイルのダウンロードで洗練された処理を行っている場合 (たとえば、サーバーがチャレンジ/レスポンス認証を行っている場合)、便利なNSDictionaryandNSArrayメソッドを使用することはできません。しかし、そのすべてが必要ない場合は、単純な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 のどれが配列でどれが辞書かはわかりません (私の例では、競技を配列にし、残りは競技 ID をキーとする辞書でした)。為に。同時実行性を最大化し、NSConditionロジックを排除し、実際に を最大限に活用しますNSOperationQueue

これは理解するには多すぎるかもしれませんが、NSCondition. あなたの現在のテクニックがうまくいくなら、それは素晴らしいことです。しかし、上記は、このような課題にどのように取り組むかを概説しています。

于 2012-12-24T21:15:34.213 に答える