reloadData処理後に処理を行いたい

#要件
`UITableView`や`UICollectionView`の再ロードにおいて、
再ロード処理が終了した後に、処理を行いたい。

#結論<font color=red>（2018/02/18修正）</font>
`UIView`のアニメーション処理を利用する解法４で、スッキリ解決しそうです。

##`UITableViewController`,`UICollectionViewController`の場合


```objc:NSBlockOperationの利用
@implementation TableViewController {
    NSBlockOperation *completionLayoutSubViews;　// 終了処理ブロック
}

// レイアウト後処理
-(void)viewDidLayoutSubviews
{
    // 処理ブロックがあれば、実行
    if (completionLayoutSubViews) {
        [completionLayoutSubViews start];
        // 実行後はクリア
        completionLayoutSubViews = nil;
    }
}

-(void)test
{    
    [self.tableView reloadData];

    // 終了処理ブロックに、再ロード終了後処理を格納
    completionLayoutSubViews = [NSBlockOperation blockOperationWithBlock:^{
        /* やりたい処理 */
    }];
}
```

`viewDidLayoutSubviews`で`reloadData`の描画イベントをキャッチ出来る為、可能な方法です。
`UITableView`,`UICollectionView`では、この方法は使えません。


##`UITableView`や`UICollectionView`の場合

こちらでは、今のところ良い手が思いつきません。
コメントのfuzzballさんの手法を参考にしてみてください。

以下、細かな解説です。

#解説
再ロード処理の`reloadData`には、
困ったことに`complation:`（終了後処理）などの指定がなく、
`UITableView`,`UICollectionView`にも、終了時イベントなどはありません。

当然、'reloadData'で発生する描画処理は呼出即実行ではないので、
以下の記載でも、「やりたい処理」は先に処理されてしまいます。

```objc:直後に記載
-(void)test
{
    [self.tableView reloadData];

    /* やりたい処理 */
}
```

##解法１
`UITableViewController`や`UICollectionViewController`ならば、
`viewDidLayoutSubviews`に記載するのも１つの手です。

```objc:viewDidLayoutSubviewsに記載
-(void)viewDidLayoutSubviews
{
    if (/*reloadData直後とわかるフラグ*/) {
        /* やりたい処理 */
    }
}
```
ただし`viewDidLayoutSubviews`は、全ての再描画処理で呼ばれるので、再ロード処理以外でも呼び出されます。（スクロールなどでも呼ばれる）

「やりたい処理」が**再ロード処理後**に**一度だけ**実行されるよう、なんらかのフラグが必須です。
フラグ管理が必要なのは面倒な話です。

そもそも、`UITableView`や`UICollectionView`だと使えない手段です。

##解法２
iOSには下記の鉄則があります。
>描画処理は、全てメインスレッドで処理しなければならない。

ふと思いました。
`reloadData`って描画処理ですよね？
それなら`reloadData`って、再ロード処理をメインスレッドに予約しているのでは？？

それならば、

```objc:直後にメインキューで記載
-(void)test
{
    [self.tableView reloadData];

    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        /* やりたい処理 */
    }];
}
```

`reloadData`の後に、
「やりたい処理」の方も、メインスレッドに予約すれば、
必ず、再ロード処理→「やりたい処理」の順序で、実行されるのでは？
という考え方です。

<font color=red>（2017/04/04追記）</font>
ダメでした。
サブスレッドでセル内容を設定する`collectionView:cellForItemAtIndexPath:`が動いているタイミングで、メインスレッドで「やりたい処理」が動きます。
`reloadData`で予約されるサブスレッド処理内で、描画処理がトリガーされるようですね。

ただし、セル数、サイズなどレイアウトに関する情報は揃っているタイミングなので、
「やりたい処理」によっては（スクロール位置の変更など）、解法2でも問題ありません。

##解法3<font color=red>（2017/04/04追記）</font>
解法１を少し改良、フラグ管理の手間を削減する案を思いつきました。

```objc:NSBlockOperationの利用
@implementation TableViewController {
    NSBlockOperation *completionLayoutSubViews;　// 終了処理ブロック
}

// レイアウト後処理
-(void)viewDidLayoutSubviews
{
    // 処理ブロックがあれば、実行
    if (completionLayoutSubViews) {
        [completionLayoutSubViews start];
        // 実行後はクリア
        completionLayoutSubViews = nil;
    }
}

-(void)test
{    
    [self.tableView reloadData];

    // 終了処理ブロックに、再ロード終了後処理を格納
    completionLayoutSubViews = [NSBlockOperation blockOperationWithBlock:^{
        /* やりたい処理 */
    }];
}
```

フラグ管理の手間も無く、確実にレイアウト処理後に実行されます。スマートでなかなか良い手です。
ただし、`UITableViewController`や`UICollectionViewController`の場合のみというのは変わりません。

##解法4<font color=red>（2018/02/18追記）</font>
wzhangさんに提案いただきました。
`animateWithDuration`の`Completion`を利用する方法で、最有力案だと思います。

```objc:animateWithDurationの利用(Objective-C)
-(void)test
{
    [UIView 
      animateWithDuration: 0.0
               animations:^{
                   // リロード
                   [self.tableView reloadData];

             } completion:^(BOOL finished) {
                 if (finished) { // 一応finished確認はしておく
                     /* やりたい処理 */
                 }
    }];
}
```
```swift:animateの利用(swift)
-func test()
{

    UIView.animate(
        withDuration: 0.0,
        animations:{
            // リロード
            self.tableView.reloadData()
        }, completion:{ finished in
            if (finished) { // 一応finished確認はしておく
                /* やりたい処理 */
            }
    });
}
```
reloadDataのラッパーにしてしまうのも良いですね。

解法３に比べ、インスタンス変数や`viewDidLayoutSubviews`の記載が不要で、`reloadData`の呼び元メソッドで完結し、
なんといっても、`UITableViewController`や`UICollectionViewController`のみの制約無しが大きいです。

#最後に
解法４でFAではないでしょうか

`animateWithDuration`が何をアニメーションと判断しているのかの点は気になりますが、
UIViewのドキュメントを見る限り、少なくともUIViewのFrameはアニメーションに含まれるので問題はないかと思います。
https://developer.apple.com/documentation/uikit/uiview