Контейнер UIViewController не освобождает его контроллеры детского просмотра
У меня есть пользовательский контейнер UIViewController, который имеет шесть дочерних UIViewControllers и набор вкладок, с которыми пользователь взаимодействует, чтобы переключаться между контроллерами дочерних представлений. Проблема в том, что когда мой диспетчер просмотра контейнера выпущен, контроллеры дочерних представлений не являются.
Я проверил, что контроллеры дочерних представлений не освобождаются, добавляя некоторый код отладки к их методам dealloc, и они освобождаются, пока их представление не добавляется в представление контроллера контейнера.
Ниже приведен фрагмент кода, который я использую для создания моего настраиваемого контроллера представления контейнеров. Указатели viewController - iVars. Я также использую 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;
}
У вас есть идеи, что я делаю неправильно?
Ответы
Ответ 1
Как я и предполагал, проблема не связана с кодом сдерживания контроллера представления в вопросе, а скорее с добавлением наблюдателей (что вы обсуждаете в своем ответе на этот вопрос):
[[NSNotificationCenter defaultCenter] addObserverForName:kUpdateEventName object:nil queue:nil usingBlock:^(NSNotification *note) {
// do stuff when update happen
}];
И вы попытались удалить его с помощью
[[NSNotificationCenter defaultCenter] removeObserver:self name:kUpdateEventName object:nil];
Итак, есть две проблемы:
-
Если вы используете 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];
Это обеспечило бы правильное удаление наблюдателя.
-
Не удалось выпустить контроллеры дочерних представлений, пока этот наблюдатель находится на своем месте, означает, что этот блок, который вы передали в 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]
Ответ 2
Это не способ добавления и удаления контроллеров детского представления
[childViewController willMoveToParentViewController:nil];
[childViewController view] removeFromSuperview];
[childViewController removeFromParentViewController];
- способ удалить и добавить его
[parentViewController addChildViewController:childViewController];
[parentViewController.view addSubview:childViewController.view];
[childViewController didMoveToParentViewController:parentViewController];
Ответ 3
После нескольких часов и попыток выяснить, что происходит, я, наконец, нашел, что привело к правильному выпуску контроллеров моего дочернего элемента.
Каждый контроллер представления имел следующее уведомление, объявленное в каждом, чтобы они могли реагировать на различные события.
[[NSNotificationCenter defaultCenter] addObserverForName:UPDATE_EVENT object:nil queue:nil usingBlock:^(NSNotification *note) {
// do stuff when update happen
}];
По какой-то причине оказывается, что мои контроллеры видят правильно. Я предполагаю, что это добавило контроллер представления в список наблюдателей NSNotificationCenters и не удалялось, когда я сделал следующую строку.
[[NSNotificationCenter defaultCenter] removeObserver:self name:UPDATE_EVENT object:nil];
Итак, чтобы исправить мою проблему, я просто изменил уведомление для регистрации, как показано ниже.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateEvent:) name:UPDATE_EVENT object:nil];
Я понятия не имею, почему так, как я регистрировал уведомление, не позволяло контроллеру моего представления правильно выходить, но это, похоже, исправило его. Если у кого-то есть представление о том, почему эта проблема произошла, пожалуйста, дайте мне знать.
Спасибо!