Разработка iPhone - Имитация предупреждения о памяти
Вопрос
Предыстория:
У меня есть приложение с панелью вкладок.Каждая вкладка содержит навигационный контроллер, позволяющий пользователю переходить от одного представления к другому, показывающий детализированную информацию о данных (каждое представление обрабатывается контроллером представления, и каждый класс контроллера представления имеет didReceiveMemoryWarning
способ).Списки заполняются путем извлечения данных из веб-служб.
Проблема:
Когда я использую опцию "Оборудование> Имитировать предупреждение о памяти" в iPhone Simulator, didReceiveMemoryWarning
метод вызывается для ВСЕХ моих контроллеров просмотра - даже для того, который просматривает пользователь.Я не хочу очищать какой-либо контент, который используется активным контроллером просмотра.Как я могу этого добиться?
Какой метод должен иметь реализацию для перезагрузки данных после того, как данные были освобождены из-за предупреждения о памяти?(Я вижу, что классы контроллера представления, которые содержат вызов табличного представления viewDidLoad
метод, когда пользователь возвращается к этому представлению, но если представление содержит (скажем, UIWebView), то viewDidLoad
метод не вызывается.Почему это?)
Отредактировано (пятница, 30 января 2009 - 03:10 вечера)
(Примечание:Я использую Interface builder для создания представлений, и loadView
метод закомментирован.)
Итак, когда контроллер представления получает предупреждающее сообщение о памяти, выполняются следующие действия:
Вызывается следующий метод:
- (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; }
В результате обращения к
[super didReceiveMemoryWarning]
,[self setView:nil]
получает автоматический вызов?Если какие-либо ресурсы должны быть очищены, то
setView
метод должен быть перезаписан, чтобы очистить локальные ресурсы.[self setView:nil]
не вызывается, если представление в данный момент активно (По умолчанию).Верно?- Мне действительно любопытно, каким методом принимается это решение и как?
Не могли бы вы, пожалуйста, подтвердить.Кроме того, я получал сообщение об ошибке, следуя этому подходу, но добавляя myObject = nil
после освобождения myObject
в dealloc
метод класса контроллера исправил проблему.Спасибо.
Решение
Это старый вопрос, но я не вижу правильного ответа, так что вот:
При получении предупреждения о памяти, -didReceiveMemoryWarning
вызывается во ВСЕХ контроллерах представления, независимо от того, являются они "текущими" или нет.Контроллеры просмотра просто прослушивают трансляцию события предупреждения о памяти.
Если представление контроллера представления не используется во время предупреждения о памяти, контроллер выгрузит его, установив для свойства значение nil.Как он узнает, используется ли представление?По виду на -superview
собственность.Если view.superview
равно нулю, представление не является частью какого-либо дерева и может быть безопасно выгружено.
Как только это произойдет, контроллер -viewDidUnload
ему звонят.Это подходящее место для выгрузки любых торговых точек и всего, что будет воссоздано в -viewDidLoad
.
Итак, что же такое -didReceiveMemoryWarning
для чего?У вашего контроллера могут быть объекты, экземпляры которых не создаются до тех пор, пока к ним не будет получен доступ.Например, у вас может быть контроллер, которому иногда требуется большой объем данных из файла, но не всегда.Вы могли бы установить для него свойство следующим образом:
- (NSData*)bigChunkOfData {
// Get data from our instance variable _data, read from disk if necessary
if (_data == nil) {
_data = [[NSData alloc] initWithContentsOfFile:@"/path/to/data"];
}
return _data;
}
При этом данные будут считаны с диска в первый раз, а затем сохранены в переменной экземпляра.С тех пор как _data
переменная создается по требованию, для нас безопасно выгружать ее в ситуациях нехватки памяти:он просто будет создан снова в следующий раз, когда нам это понадобится.
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
[_data release];
_data = nil; // <-- Very important: don't leave strong references dangling.
}
Другие советы
Я делаю свою уборку вот так:
-(void)setView:(UIView*)view
{
[super setView:view];
if(view == nil)
{
// Our view has been cleared, therefore we should clean up everything
// we are not currently using
....
setView:nil
вызывается UIViewController в ответ на предупреждение памяти, если это представление в данный момент не отображается - это в основном то, что вы хотите знать.
ОТРЕДАКТИРОВАННЫЙ
В ответ на последующие действия:
- Правильно.
- Это то, что я делаю, и у меня это работает.
Правильно.Реализация
didReceiveMemoryWarning
в UIViewController это то, что делает это.Если вы не переопределитеdidReceiveMemoryWarning
, затем будет вызвана реализация базового класса в UIViewController - если вы переопределите ее, очевидно, вам следует вызвать:[super didReceiveMemoryWarning]
Чтобы гарантировать, что мне не придется обрабатывать это для каждого отдельного viewcontroller, который я пишу..Я только что создал шаблон Xcode ViewController, который предоставляет рекомендации относительно того, какие объекты выпускать и когда..
более подробное объяснение здесь http://iphone2020.wordpress.com/2010/05/30/efficient-memory-handling-in-uiviewcontroller-part-1/
Надеюсь, это окажется полезным.
Что касается предупреждений об управлении представлением и памятью:
UIKit не только разрешает обратную навигацию с контроллера представления, но также позволяет осуществлять навигацию к другим контроллерам представления из существующих.В таком случае будет выделен новый UIViewController, а затем загружен в view.Старый контроллер представления исчезнет с экрана и станет неактивным, но по-прежнему будет владеть многими объектами – некоторыми в пользовательских свойствах и переменных, а другими в свойствах / иерархии представления.То же самое делает новый контроллер видимого представления в отношении своих объектов просмотра.
Из-за ограниченного объема памяти мобильных устройств владение двумя наборами объектов – одним в контроллере просмотра вне экрана и другим в контроллере просмотра на экране – может оказаться слишком сложным для обработки.Если UIKit сочтет это необходимым, он может освободить часть памяти контроллера закадрового просмотра, которая в любом случае не отображается;UIKit знает, какой контроллер просмотра находится на экране, а какой за его пределами, поскольку, в конце концов, именно он управляет ими (когда вы вызываете presentModalViewController:animated:
или dismissModalViewControllerAnimated:
).Таким образом, каждый раз, когда на него оказывается давление, UIKit генерирует предупреждение о памяти, которое выгружает и освобождает ваш закадровый вид из иерархии представлений, затем вызывает ваш пользовательский метод viewDidUnload, чтобы вы сделали то же самое для ваших свойств и переменных.UIKit автоматически выпускает self.view, позволяя нам затем вручную освобождать наши переменные и свойства в нашем коде viewDidUnload.Он делает это для всех контроллеров закадрового просмотра.
Когда в системе заканчивается память, она запускает didReceiveMemoryWarning
.Закадровые просмотры будут восстановлены и освобождены после предупреждения памяти, но ваш экранный просмотр не будет освобожден – он виден и необходим.В случае, если вашему классу принадлежит много памяти, такой как кэши, изображения и тому подобное, didReceiveMemoryWarning
это то место, где вы должны удалить их, даже если они находятся на экране;в противном случае ваше приложение может быть закрыто из-за переизбытка системных ресурсов.Вам нужно переопределить этот метод, чтобы убедиться, что вы очистили свою память;просто помни, что ты звонишь [super didReceiveMemoryWarning];
.
Еще более подробное объяснение доступно здесь: http://myok12.wordpress.com/2010/11/30/custom-uiviewcontrollers-their-views-and-their-memory-management/
К счастью, симулятор имеет удобную функцию, которая позволяет вам тестировать ситуации с нехваткой памяти.Поместите некоторые инструкции NSLog() как в viewDidLoad, так и в didReceiveMemoryWarning, например:
- (void)viewDidLoad {
NSLog(@"viewDidLoad");
...
}
- (void)didReceiveMemoryWarning {
NSLog(@"didReceiveMemoryWarning");
}