14

6 つの子 UIViewControllers を持つカスタム コンテナー UIViewController と、ユーザーが子ビュー コントローラーを切り替えるために操作する一連のタブがあります。問題は、コンテナー ビュー コントローラーが解放されたときに、子ビュー コントローラーが解放されないことです。

子ビュー コントローラーは、dealloc メソッドにデバッグ コードを追加することによって解放されないことを確認しました。ビューがコンテナー ビュー コントローラーのビューに追加されない限り、子ビュー コントローラーは解放されます。

以下は、カスタム コンテナー ビュー コントローラーを作成するために使用しているコードの抜粋です。viewController ポインターは iVar です。私も ARC を使用しているため、実際のリリース コールはありません。

- (void)init 
{
    if ((self = [super init])) { 
        vc1 = [[UIViewController alloc] init];
        [self addChildViewController:vc1];

        vc2 = [[UIViewController alloc] init];
        [self addChildViewController:vc2];

        vc3 = [[UIViewController alloc] init];
        [self addChildViewController:vc3];

        vc4 = [[UIViewController alloc] init];
        [self addChildViewController:vc4];

        vc5 = [[UIViewController alloc] init];
        [self addChildViewController:vc5];

        vc6 = [[UIViewController alloc] init];
        [self addChildViewController:vc6];
    }
    return self;
}

- (void)dealloc
{
    [vc1 removeFromParentViewController];
    vc1 = nil;

    [vc2 removeFromParentViewController];
    vc2 = nil;

    [vc3 removeFromParentViewController];
    vc3 = nil;

    [vc4 removeFromParentViewController];
    vc4 = nil;

    [vc5 removeFromParentViewController];
    vc5 = nil;

    [vc6 removeFromParentViewController];
    vc6 = nil;
}

- (void)switchFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController
{
    if (fromViewController) {
        [fromViewController.view removeFromSuperview];
    }

    [self.view addSubview:toViewController];
    toViewController.view.frame = self.view.bounds;
}

私が間違っていることについて何か考えがありますか?

4

3 に答える 3

26

これは子View Controllerを追加および削除する方法ではありません

    [childViewController willMoveToParentViewController:nil];
    [childViewController view] removeFromSuperview];
    [childViewController removeFromParentViewController];

削除して追加する方法です

    [parentViewController addChildViewController:childViewController];
    [parentViewController.view addSubview:childViewController.view];
    [childViewController didMoveToParentViewController:parentViewController];
于 2012-12-12T16:54:54.163 に答える
12

私が疑ったように、問題は質問のView Controller封じ込めコードに関連しているのではなく、オブザーバーの追加に関連しています(この質問への回答で説明しています):

[[NSNotificationCenter defaultCenter] addObserverForName:kUpdateEventName object:nil queue:nil usingBlock:^(NSNotification *note) {
    // do stuff when update happen
}];

そして、あなたがそれを取り除こうとしたこと

[[NSNotificationCenter defaultCenter] removeObserver:self name:kUpdateEventName object:nil];

したがって、次の 2 つの問題があります。

  1. を使用する場合addObserverForName:object:queue:、これはこのオブザーバーを削除する正しい方法ではありません。代わりに、オブザーバーを追跡するプロパティを定義します。

    @property (nonatomic, weak) id<NSObject> notification;
    

    次に、作成時にそのオブザーバーへの参照を保存します。

    self.notification = [[NSNotificationCenter defaultCenter] addObserverForName:kUpdateEventName object:nil queue:nil usingBlock:^(NSNotification *note) {
        // do something
    }];
    

    削除する場合は、次のリファレンスを使用してください。

    [[NSNotificationCenter defaultCenter] removeObserver:self.notification];
    

    これにより、オブザーバーが適切に削除されることが保証されます。

  2. このオブザーバーが配置されている間に子ビュー コントローラーが解放されなかったということは、渡したこのブロックが へaddObserverForName:object:queue:の参照を持っている必要があることを意味しますself。でこのオブザーバを正しく (上記のように) 削除しようとしてdeallocも、強い参照サイクル (以前は保持サイクルと呼ばれていました) が残ります。これはいくつかの方法で解決されますが、最も堅牢なパターンは、最初に次のweakSelfパターンを使用して強い参照サイクルを防ぐことです。

    typeof(self) __weak weakSelf = self;
    
    self.notification = [[NSNotificationCenter defaultCenter] addObserverForName:kFooNotification object:nil queue:nil usingBlock:^(NSNotification *note) {
        // use `weakSelf` in this block; not `self`
    }];
    

私の元の答えは以下のとおりです。


Srikanth は正しいのですが、後でaddChildViewControllerコールする必要がdidMoveToParentViewController:selfあり、コールする前にコールremoveFromParentViewControllerする必要がありますwillMoveToParentViewController:nil。しかし、それはあなたの問題ではありません。実際、私はあなたのコードのバリエーションを使用しました ( dealloc.

要するに、あなたの問題は別の場所にあるのではないかと思います。おそらくどこかで保持サイクルが発生しています。たとえば、あなたの子供は親を強く参照していますか? 繰り返しタイマーを使用していますか? いくつかのタブを参照します。タブ バー コントローラーを使用していませんね。それはそのようなものでなければなりません。

[OPのコードサンプルのコードスニペットとマイナーな詳細を含む元の回答の残りを見たい場合は、改訂履歴を参照してください]

于 2012-12-12T17:43:46.893 に答える
2

何が起こっているのかを何時間も把握しようとした後、最終的に子のView Controllerが正しく解放されない原因を見つけました。

各View Controllerには、さまざまなイベントに応答できるように、それぞれに次の通知が宣言されていました。

[[NSNotificationCenter defaultCenter] addObserverForName:UPDATE_EVENT object:nil queue:nil usingBlock:^(NSNotification *note) {
    // do stuff when update happen
}];

なんらかの理由で、View Controller が正しくリリースされないことが判明しました。これにより、ビューコントローラーが NSNotificationCenters オブザーバーリストに追加され、次の行を実行したときに削除されなかったと思います。

[[NSNotificationCenter defaultCenter] removeObserver:self name:UPDATE_EVENT object:nil];

私の問題を解決するために、通知を以下のように登録するように変更しました。

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateEvent:) name:UPDATE_EVENT object:nil];

通知を登録する方法でView Controllerが正しくリリースされなかった理由はわかりませんが、これで修正されたようです。この問題が発生する理由について誰かが洞察を持っている場合は、私に知らせてください。

ありがとう!

于 2012-12-12T18:45:03.330 に答える