Вопрос по initialization, uiviewcontroller, uikit, ios, iphone – viewDidLoad фактически вызывается каждый раз, когда происходит переход

9

Я видел много сообщений о переполнении стека, в которых говорится, что метод контроллеров viewDidLoad вызывается только при первом обращении к контроллеру и не обязательно каждый раз, но всегда хотя бы один раз.

Это совсем не то, что я вижу! Я собрал простой тест, чтобы выделить это: https://github.com/imuz/ViewDidLoadTest

Похоже, для сегментов контроллера навигации и модальных представлений всегда вызывается viewDidLoad. Единственный раз, когда он не вызывается, это при переключении между вкладками.

Каждое объяснение viewDidLoad, которое я могу найти, противоречит этому:

When is viewDidLoad called? UIViewController viewDidLoad vs. viewWillAppear: What is the proper division of labor? http://www.manning-sandbox.com/thread.jspa?threadID=41506

А собственная документация Apple указывает, что представление выгружается только при нехватке памяти.

В настоящее время я делаю инициализацию в viewDidLoad, предполагая, что он вызывается при каждом переходе segue.

Я что-то здесь упускаю?

Ваш Ответ

3   ответа
12

Phillip Mills & apos; ответ правильный. Это только его улучшение.

Система работает как задокументировано.

Вы видите viewDidLoad, потому что контроллер представления, помещаемый в контроллер навигации, являетсяnew пример. Этоmust вызовите viewDidLoad.

Если вы исследуете немного дальше, вы увидите, что каждый из этих контроллеров представления освобождается, когда они выталкиваются (просто установите точку останова или NSLog в dealloc). Это освобождение не имеет никакого отношения к контейнеру контроллера представления ... оно не контролирует срок службы используемого им контроллера ... оно просто содержит в себе сильную ссылку на него.

Когда контроллер извлекается из стека навигационных контроллеров, навигационный контроллер освобождает свою ссылку, и поскольку других ссылок на него нет, контроллер представления освобождается.

Контроллер навигации содержит только сильные ссылки на контроллеры представления, которые находятся в его активном стеке.

Если вы хотите повторно использовать тот же контроллер,you несут ответственность за его повторное использование. Когда вы используете сегменты раскадровки, вы отказываетесь от этого контроля (в значительной степени).

Допустим, у вас естьpush перейти к просмотру контроллераFoo в результате нажатия какой-то кнопки. Когда эта кнопка нажата, «система» создаст экземплярFoo (контроллер представления назначения), а затем выполните переход. Контейнер контроллера теперь содержит единственную сильную ссылку на этот контроллер представления. Как только это будет сделано, VC будет освобожден.

Так как он создает новый контроллер каждый раз,viewDidLoad будет вызываться каждый раз, когда будет представлен контроллер.

Теперь, если вы хотите изменить это поведение и кэшировать контроллер представления для последующего повторного использования, вы должны сделать это специально. Если вы не используете сегменты раскадровки, это легко, так как вы фактически толкаете / вставляете VC в контроллер Nav.

Однако, если вы используете сегменты раскадровки, это немного больше проблем.

Есть несколько способов сделать это, но все они требуют некоторой формы взлома. Сама раскадровка отвечает за создание новых контроллеров представления. Одним из способов является переопределениеinstantiateViewControllerWithIdentifier, Это метод, который вызывается, когда segue необходимо создать контроллер представления. Он вызывается даже для контроллеров, которым вы не присваиваете идентификатор (система предоставляет вымышленный уникальный идентификатор, если вы его не присваиваете).

Обратите внимание, я надеюсь, что это в основном в образовательных целях. Я, конечно, не предлагаю это как лучший способ решения ваших проблем, какими бы они ни были.

Что-то вроде...

@interface MyStoryboard : UIStoryboard
@property BOOL shouldUseCache;
- (void)evict:(NSString*)identifier;
- (void)purge;
@end
@implementation MyStoryboard
- (NSMutableDictionary*)cache {
    static char const kCacheKey[1];
    NSMutableDictionary *cache = objc_getAssociatedObject(self, kCacheKey);
    if (nil == cache) {
        cache = [NSMutableDictionary dictionary];
        objc_setAssociatedObject(self, kCacheKey, cache, OBJC_ASSOCIATION_RETAIN);
    }
    return cache;
}
- (void)evict:(NSString *)identifier {
    [[self cache] removeObjectForKey:identifier];
}
- (void)purge {
    [[self cache] removeAllObjects];
}
- (id)instantiateViewControllerWithIdentifier:(NSString *)identifier {
    if (!self.shouldUseCache) {
        return [super instantiateViewControllerWithIdentifier:identifier];
    }
    NSMutableDictionary *cache = [self cache];
    id result = [cache objectForKey:identifier];
    if (result) return result;
    result = [super instantiateViewControllerWithIdentifier:identifier];
    [cache setObject:result forKey:identifier];
    return result;
}
@end

Теперь вы должны использовать эту раскадровку. К сожалению, хотя UIApplication поддерживает основную раскадровку, он не предоставляет API для его получения. Тем не менее, каждый контроллер представления имеет метод,storyboard чтобы получить раскадровку, из которой он был создан.

Если вы загружаете свои собственные раскадровки, просто создайте экземпляр MyStoryboard. Если вы используете раскадровку по умолчанию, то вам нужно заставить систему использовать ваш специальный. Опять же, есть много способов сделать это. Один простой способ - переопределить метод доступа раскадровки в контроллере представления.

Вы можете сделать MyStoryboard прокси-классом, который перенаправляет все в UIStoryboard, или вы можете быстро переключиться на основную раскадровку, или вы можете просто заставить свой локальный контроллер вернуть один из метода раскадровки.

Запомните, здесь есть проблема. Что если вы поместите один и тот же контроллер представления в стек более одного раза? С кешем один и тот же объект контроллера представления будет использоваться несколько раз. Это действительно то, что вы хотите?

Если нет, то теперь вам нужно управлять взаимодействием с самими контейнерами контроллера, чтобы они могли проверить, не известен ли им этот контроллер, и в этом случае необходим новый экземпляр.

Так чтоis способ получить кэшированные контроллеры при использовании сегментов раскадровки по умолчанию (на самом деле есть несколько способов) ... но это не обязательно хорошая вещь, и уж точно не то, что вы получаете по умолчанию.

@Odelya Да, но любое состояние, которое у вас может быть в вашем контроллере вида, также будет перезаписано. Хитрость заключается в том, чтобы избежать повторного использования контроллеров, которые уже находятся в стеке, потому что вы испортите их переменные экземпляра.
Ааа прямо в IOS6. Похоже, что нет никакой реальной точки зрения для viewDidLoad, это также может быть конструктор. Я так понимаю, это означает, что предупреждения памяти больше не будут вызывать viewDidUnload? Это что-то меняет, так как это означает, что viewDidLoad вызывается только один раз? У меня нет IOS6 на данный момент для тестирования. Imran
@JodyHagins, что не так с использованием того же кэшированного UIViewController? будут ли вызваны все методы, кроме viewDidLoad?
На самом деле, viewWillUnload и viewDidUnload устарели, так что вам не стоит вставлять код в будущем. Обрабатывать didReceiveMemoryWarning для освобождения ресурсов, когда они находятся под давлением памяти.
Спасибо за подробный ответ. Я в порядке, не кэшируя контроллеры. Я просто хотел немного больше понять цикл загрузки-выгрузки, чтобы точно понять, что я могу и не могу вставить в viewDidLoad. Теперь все намного понятнее. Imran
0

Он вызывается каждый раз, когда представление контроллера загружается с нуля (т.е. запрошено, но еще не доступно). Если вы освобождаете контроллер, и представление идет вместе с ним, то он будет вызван снова при следующем создании экземпляра контроллера (например, когда вы создаете контроллер для его принудительной установки модально или посредством segue). Контроллеры представления во вкладках не освобождаются, потому что контроллер вкладок хранит их.

11

Я полагаю, что документация Apple описывает ситуацию, когда контроллер представления не освобождается. Если вы используете segue, то вы вызываете создание нового конечного контроллера и, будучи новым объектом, ему нужно загрузить представление.

В приложениях на основе XIB я иногда кэшировал объект контроллера, который, как я знал, мог бы часто использовать повторно. В этих случаях они вели себя в соответствии с документацией с точки зрения того, когда необходимо загружать представление.

Редактировать: Читая ссылки, которые вы включили, я не вижу в них никакого противоречия. Они также говорят о вещах, которые происходят в течение срока службы объекта контроллера представления.

Да, он выгружается, обновил мой тестовый проект:github.com/imuz/ViewDidLoadTest. Imran
unload не будет вызываться, если вызывается dealloc ...
Чтобы проверить поведение нехватки памяти (на симуляторе), нужно установить контроллер вида, накрыть его модальным контроллером вида и использовать опцию Hardware-> gt; Simulate Memory Warning. Вид скрытого контроллера должен быть выгружен, а затем повторно загружен, когда модальное окно закрыто.
интересно, я попробую. Я полагаю, что любое представление, которое в данный момент не активно, является кандидатом на выгрузку. Imran
Это биты, в которых они говорят о том, что представления выгружаются при «условиях низкой памяти». подразумевая, что они оставлены вокруг по умолчанию, который я не видел, чтобы иметь место. Так что на самом деле это зависит от реализации родительского контроллера. Если вы используете контроллеры навигации, каждый раз создается и загружается новый экземпляр. Не так с tabcontrollers. Imran

Похожие вопросы