10

私はちょうどを呼び出した後でもUIViewControllerがdeallocされないように、非常に厄介なUIViewControllerのデバッグを終了しました。私はperformBatchUpdatesに電話をかけると、すぐにdismissViewControllerAnimated、のUIViewControllerが漏洩しますと、そのUIViewControllerdeallocメソッドを決して呼び出していない場合は、performBatchUpdatesの親UIViewControllerへの強い参照がアクティビティをリークするのはなぜですか?

self.dataSource.doNotAllowUpdates = YES; 

    [self.collectionView performBatchUpdates:^{ 
     [self.collectionView reloadItemsAtIndexPaths:@[indexPath]]; 
    } completion:^(BOOL finished) { 
     self.dataSource.doNotAllowUpdates = NO; 
    }]; 

基本的には:

は、私は次のコードブロックに問題を突き止め呼び出される。 UIViewControllerは永遠にハングアップします。

誰かがこの現象を説明できますか?私は、がある時間間隔、例えば500ミリ秒で実行されると仮定しているので、上記の間隔の後に、これらのメソッドを呼び出し、deallocをトリガすると仮定します。

修正はこのように表示されます。

self.dataSource.doNotAllowUpdates = YES; 

    __weak __typeof(self)weakSelf = self; 

    [self.collectionView performBatchUpdates:^{ 
     __strong __typeof(weakSelf)strongSelf = weakSelf; 

     if (strongSelf) { 
      [strongSelf.collectionView reloadItemsAtIndexPaths:@[indexPath]]; 
     } 
    } completion:^(BOOL finished) { 
     __strong __typeof(weakSelf)strongSelf = weakSelf; 

     if (strongSelf) { 
      strongSelf.dataSource.doNotAllowUpdates = NO; 
     } 
    }]; 

BOOLメンバ変数、doNotAllowUpdatesつまり、私はperformBatchUpdatesへの呼び出しが実行されている間、そのデータソース/ collectionView更新のいずれかの種類を防ぐ追加の変数です。

performBatchUpdatesにweakSelf/strongSelfパターンを使用する必要があるかどうかについては、オンラインでディスカッションしましたが、特にこの質問には何も見つかりませんでした。

私はこのバグの底に到達できたことをうれしく思いますが、私は見ているこの動作を私に説明するためにスマートなiOS開発者を愛するでしょう。

+0

更新サイクルかどうかを確認するのは興味深いことです。どちらもどちらも自分のブロックを恒久的に保持してはならないと思います。コレクションビューをスーパービューから削除すると、バッチ更新が実行されなくなり、完了ブロックが呼び出されたり解放されることはありません。私の意見ではレーダーに値する。 – jrturton

+0

@jrturtonこれは最も可能性の高い説明のようです!この洞察に基づいてデバッガでこれを再現できるかどうかがわかります。 – esilver

+0

@jrturtonはデバッガで再解析できません...少なくとも両方のブロックが常に呼び出されるようです。途中で却下された場合は、内部がブロックを無視している可能性はありますか? – esilver

答えて

-1

あなたが把握したように、weakが使用されていない場合、保持サイクルが作成されます。

は、collectionViewを強く参照しており、collectionViewは、selfを強く参照しています。

非同期ブロックが実行される前に、常にselfが割り当て解除されている可能性があります。安全にこれを処理するために二つのことを実行する必要があります。

  1. 常に
  2. は常にweakSelfnunnull のparam

としてそれを渡す前に存在することを確認selfへの弱参照(またはIVAR自体)を使用します更新日:

ロギングの少しを入れてperformBatchUpdatesは多くを確認します:

- (void)logPerformBatchUpdates { 
    [self.collectionView performBatchUpdates:^{ 
     NSLog(@"starting reload"); 
     [self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]]; 
     NSLog(@"finishing reload"); 
    } completion:^(BOOL finished) { 
     NSLog(@"completed"); 
    }]; 

    NSLog(@"exiting"); 
} 

プリント:

starting reload 
finishing reload 
exiting 
completed 

これは完了ブロックは、それが非同期バックメインスレッドにディスパッチされることを意味現在のスコープを出た後に焼成することを示しています。

バッチ更新を実行した後すぐにView Controllerを終了すると記載されています。私はこれがあなたの問題の根源だと思う:

いくつかのテストの後、私がメモリリークを再現できる唯一の方法は、解雇の前に仕事を派遣することでした。これは、ロングショットですが、あなたのコードは、偶然、このように見えるん?:

- (void)breakIt { 
    // dispatch causes the view controller to get dismissed before the enclosed block is executed 
    dispatch_async(dispatch_get_main_queue(), ^{ 
     [self.collectionView performBatchUpdates:^{ 
      [self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]]; 
     } completion:^(BOOL finished) { 
      NSLog(@"completed: %@", self); 
     }]; 
    }); 
    [self.presentationController.presentingViewController dismissViewControllerAnimated:NO completion:nil]; 
} 

上記のコードはdeallocビューコントローラ上で呼び出されていないにつながります。

既存のコードを使用して、単にdismissViewControllerコールを送信すると、問題を修正する可能性があります(またはperformSelector:after :)。

+0

上記の私のコードスニペットからわかるように、あなたはすべての面で正しいです。完了ブロックが実行され、保持サイクルが解除された後にクリーンアップされます。保持サイクルがどのように作成され、UIViewControllerが決して割り当て解除されないのかは明らかではありません。上記の特定のコードスニペットを与えてください。 – esilver

+0

保持サイクルはかなり明確にすべきである。自己はコレクションビューを保持し、コレクションビューは自己を保持するようになりました。これらのポインタの1つは、保持サイクルを防ぐために弱い必要があります(したがって、 'weakSelf'を作成します)。 UICollectionViewはブラックボックスなので、クリーンアップが何を参照しているのかよく分かりません...私はcollectionViewが完了ブロックを除外するとは思わないでしょうし、それに頼るべきではありません。 – Casey

+0

あなたのロジックを完成させましょう:UICollectionViewは完成ブロックを除外します。 UICollectionViewはバッチ更新の実行を開始し、最初に(強く保持されている)ブロックを呼び出します。 UIViewControllerは終了しますが、2番目の補完ブロックが強い参照を持つため、クリーンアップできません。 100ミリ秒アニメーション効果のために行く。 UICollectionViewはその完了ブロックを呼び出します。さて、すべてのブロックが実行され、上記のブロックへの参照はクリーンアップされなければなりません。そして、UIViewControllerはそれ以上リファレンスを持っておらず、deallocできます。これは単に遅延させるべきであり、防止するのではなく、解除する必要があります。右? – esilver

0

これはUICollectionViewのバグのようです。 APIユーザーは、タスクの実行を超えてシングルランブロックパラメータが保持されることを期待しないでください。したがって、参照サイクルを防止することは問題ではありません。

UICollectionViewは、バッチ更新プロセスが終了すると、またはバッチ更新プロセスが中断された場合(たとえば、コレクションビューが画面から削除された場合など)、ブロックへの参照をすべて消去する必要があります。

更新プロセス中にコレクションビューがオフスクリーンで表示されても完了ブロックが呼び出されるので、コレクションビューではその完了ブロックまでの参照をすべて埋める必要がありますコレクションビューの現在の状態にかかわらず、再び呼び出されることはありません。

関連する問題