2017-12-23 7 views
0

サーバからデータをロードしていますが、実際にデータがロードされる前にループ(for .. in ..)が終了しています。どのようにメインキュー上の実装ディスパッチを修正するか、別のことを間違えている?ブロック関数ObjCでディスパッチメインキュータスクを修正するには?

[ApiManager getCategoriesifSuccess:^{ 
    [self.sectionArray addObjectsFromArray:[CategoryManager getCats]]; 
    for (CategorySectionModel *mod in [CategoryManager getCats]) { 
     [self.arrayForBool addObject:@"YES"]; 
     [ApiManager getCatalogItemsInCity:currentCity withSection:mod.uid 
           andStart:@"1" andLimit:@"20" ifSuccess:^{ 
            [self.itemsArray setObject:[CatalogItemManager getItems] forKey:mod.uid]; 
            [indicator stopAnimating]; 
            [self.tableView reloadData]; 
            NSLog(@"DONE WITH SECTION:%@", mod.title); 
           } orIfFailed:^(NSString *fail) { 
            NSLog(@"%@", fail); 
           }]; 
     NSLog(@"LOOP"); 
    }; 
} orIfFailed:^{ 
}]; 

そして、このコンソールのリターンが、それが正しいではないようだ。

2017-12-24 01:17:57.148355+0300 iOS[75679:1805296] LOOP 
2017-12-24 01:17:57.149576+0300 iOS[75679:1805296] LOOP 
2017-12-24 01:17:57.150786+0300 iOS[75679:1805296] LOOP 
2017-12-24 01:17:57.152773+0300 iOS[75679:1805296] LOOP 
2017-12-24 01:17:57.154727+0300 iOS[75679:1805296] LOOP 
2017-12-24 01:17:57.156987+0300 iOS[75679:1805296] LOOP 
2017-12-24 01:17:57.158840+0300 iOS[75679:1805296] LOOP 
2017-12-24 01:17:57.981119+0300 iOS[75679:1805296] DONE WITH SECTION:First 
2017-12-24 01:17:58.285569+0300 iOS[75679:1805296] DONE WITH SECTION:Second 
2017-12-24 01:17:58.403725+0300 iOS[75679:1805296] DONE WITH SECTION:Third 
2017-12-24 01:17:58.434170+0300 iOS[75679:1805296] DONE WITH SECTION:Fourth 
2017-12-24 01:17:58.449970+0300 iOS[75679:1805296] DONE WITH SECTION:Fifth 
2017-12-24 01:17:58.469519+0300 iOS[75679:1805296] DONE WITH SECTION:Sixth 
2017-12-24 01:17:58.535116+0300 iOS[75679:1805296] DONE WITH SECTION:Seventh 

答えて

1

使用ディスパッチグループ:

スウィフト(擬似コード):

let group = DispatchGroup() 

    for ... { 
     group.enter() 

     someAsyncMethod({ 
      group.leave() 
     }) 
    } 
    group.notify(queue: DispatchQueue.main, execute: { 
     // you code here will be invoked after all group.leave() 
    }) 

はあなたのことを確認してくださいgroup.enter()ごとに1つだけ(!)group.leave()を呼び出します。失敗したコールバックがある場合は、成功と失敗のコールバックを呼び出す必要があります。group.leave()

+0

質問は特にObjective-Cについてです。 –

+1

私は何も変わっていないようです。 –

+1

@IlyaChikmarev - いいえ、これは重大な変更です。すべてのリクエストがいつ完了したかを知ることが意図されていると想定しています(その時点で、他のアクションを引き起こす可能性があります)。ディスパッチグループを使用するのが正しい方法です(ただし、私はAlexanderがObjective-Cの例をあなたに提供したかったと思いますが)。 – Rob

1

この動作は意図されており、非同期ディスパッチが行われるものです。このような場合には

[Manager doSomethingLongInBackgroundWithCompletionHandler: 
^{ 
    NSLog(@"done"); 
}]; 
NSLog(@"Loop"); 

長い背景がかかる一方で、メイン制御フローの実行はすぐに続けているため、あなたは秒、分、時間がかかることがあり、最初のループが表示されます。簡単な例にそれを軽減してみましょう。その問題を解決するために

(これは意図した動作であるため、それは、問題ではありません)、あなたはそれに完了した後に実行する必要があるコードを配置する必要があります:あなたは

[Manager doSomethingLongInBackgroundWithCompletionHandler: 
^{ 
    NSLog(@"done"); 
    NSLog(@"Loop"); 
}]; 

ログが完了ハンドラ内で移動させなければならないことを意味する場合:

for (CategorySectionModel *mod in [CategoryManager getCats]) { 
    [self.arrayForBool addObject:@"YES"]; 
    [ApiManager getCatalogItemsInCity:currentCity withSection:mod.uid andStart:@"1" andLimit:@"20" ifSuccess: 
    ^{ 
     [self.itemsArray setObject:[CatalogItemManager getItems] forKey:mod.uid]; 
     [indicator stopAnimating]; 
     [self.tableView reloadData]; 
     NSLog(@"DONE WITH SECTION:%@", mod.title); 
     NSLog(@"LOOP"); 
    } 
    orIfFailed: 
    ^(NSString *fail) 
    { 
     NSLog(@"%@", fail); 
     NSLog(@"LOOP"); 
    }]; 
}; 

私はNSLog(@"LOOP")は、より関連性の高いコードのプレースホルダであること、を前提としています。

1

あなたが記述した動作は正しいものであり、予期されるはずです。

非同期タスクのループを実行しています。つまり、あなたはループしていて、非同期タスクの束を始めますが、ネットワーク要求がゆっくりと進行している間にループは直ちに終了します。だから、例えば、あなたのindicatorを止めることができるように、それらのネットワークリクエストがいつ完了するかを知るための何らかの方法が必要です。

代表的な解決方法はディスパッチグループです。

[indicator startAnimating]; 

[ApiManager getCategoriesifSuccess:^{ 
    dispatch_group_t group = dispatch_group_create(); 

    NSArray *categories = [CategoryManager getCats]; 
    [self.sectionArray addObjectsFromArray:categories]; 

    for (CategorySectionModel *mod in categories) { 
     dispatch_group_enter(group); 

     [self.arrayForBool addObject:@"YES"]; 
     [ApiManager getCatalogItemsInCity:currentCity withSection:mod.uid andStart:@"1" andLimit:@"20" ifSuccess:^{ 
      [self.itemsArray setObject:[CatalogItemManager getItems] forKey:mod.uid]; 
      NSLog(@"DONE WITH SECTION:%@", mod.title); 

      // personally I'd reload section by section, e.g. 
      // 
      // NSInteger section = ... 
      // [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:section] withRowAnimation:UITableViewRowAnimationFade]; 

      dispatch_group_leave(group); 
     } orIfFailed:^(NSString *fail) { 
      NSLog(@"%@", fail); 
      dispatch_group_leave(group); 
     }]; 
    }; 

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{ 
     [indicator stopAnimating]; 
     [self.tableView reloadData]; // assuming you didn't just reload section-by-section as they finished 
    }); 
} orIfFailed:^{ 
    [indicator stopAnimating]; 
    // display something about the nature of the error 
}]; 

は、私はこれらの様々な方法がありそうな同時実行をサポートするように設計されていないことを、あなたはより深い問題を抱えている疑いがある、これを言って(すなわち、同時に起こっていくつかの非同期要求があるかもしれません)。たとえば、特定のカテゴリのアイテムを取得するには、 [CatalogItemManager getItems]を呼び出しています。しかし、同時に複数のリクエストがある場合、それは機能しません。あなたは実際に getCatalogItemsInCityを(a)いくつかのローカル変数に保持するように変更する必要があります。 (b) successブロックのパラメータとして戻します。

(同様にgetCategoriesifSuccessは、おそらく私はあなたが同時に起こっているカテゴリの複数のクエリを持っていない疑いがあるので、それは()がそれほど重要なのです。[CategoryManager getCats]に依存しない、そのsuccessブロックにパラメータとしてカテゴリを渡す必要がありますが、それはまだですより良いデザイン。)のように見えるかもしれません

:あなたの完了ハンドラが戻ってメインキューにか派遣されている場合

[ApiManager getCategoriesIfSuccess:^(NSArray *categories){ 
    dispatch_group_t group = dispatch_group_create(); 

    [self.sectionArray addObjectsFromArray:categories]; 

    for (CategorySectionModel *mod in categories) { 
     dispatch_group_enter(group); 

     [self.arrayForBool addObject:@"YES"]; 
     [ApiManager getCatalogItemsInCity:currentCity withSection:mod.uid andStart:@"1" andLimit:@"20" ifSuccess:^(NSArray *items){ 
      [self.itemsArray setObject:items forKey:mod.uid]; 
      NSLog(@"DONE WITH SECTION:%@", mod.title); 

      NSInteger section = ... 
      [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:section] withRowAnimation:UITableViewRowAnimationFade]; 

      dispatch_group_leave(group); 
     } orIfFailed:^(NSString *fail) { 
      NSLog(@"%@", fail); 
      dispatch_group_leave(group); 
     }]; 
    }; 

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{ 
     [indicator stopAnimating]; 
    }); 
} orIfFailed:^{ 
    [indicator stopAnimating]; 
    // display something about the nature of the error 
}]; 

はところで、私は知りません。上記のように、私はそうであると仮定しましたが、そうでない場合は、すべてのモデルとUIアップデートをメインキューに手動でディスパッチする必要があります。

+0

本当に良い説明とあなたの考えのために正しいです!今私のためのより明確な。それは大いに役立ちます! –

関連する問題