Некоторые из вас, возможно, заметили, что viewControllers больше не запрашивают viewWillUnload и viewDidUnload в iOS6. Это происходит потому, что контроллеры больше не выгружают свои представления (view) автоматически.
Ваша первая мысль может быть «Окей, как я могу вручную выгрузить мое представление при предупреждении о недостатке памяти (low memory warning)? Это выглядит, как шаг назад.»
Затем вы ищите ответы и пишите что-то вроде:
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
if([self isViewLoaded] && ![[self view] window]) {
[self setView:nil];
}
}
Однако такой метод использовать не нужен, это потенциально вредно из-за некоторых изменений в базовой поддержки UIView. Поэтому на практике вы редко вручную должны выгружать из памяти представление контроллера при сообщении о недостатке памяти. (А в теории — никогда)
Но почему? Если вы внимательно читали книги по программированию на iOS, то знаете, что UIView подкласс UIResponder (чтобы представление могло взаимодействовать с событиями (events)) и имеет указатель на экземпляр своего CALayer (чтобы представление могло быть отрисовано на экране).
CALayer — это контейнер для битмапа изображения (bitmap image). Когда UIView отрисовывается в методе drawInRect: он создает битмап для своего слоя (layer). Остальные переменные слоя (многие взяты из представления, такие как frame и backgroundColor) указывают как и где это изображение находится на экране.
Но основная часть слоя (с точки зрения использования памяти) это битмап. Сам слой это 48 байт, а стандартный UIView всего лишь 96 байт, вне зависимости от размера экрана. При этом потребление памяти для слоя зависит от размеров битмапа изображения на экране. Например, для iPad Retina изображение на полной экран может достигать 12мб.
Подход, применяемый в iOS6 состоит в том, что при нехватке памяти битмап слоя (layer) выгружается из памяти, а объекты UIView и CALayer сохраняются в целости. Это вполне целесообразно, т.к. сами объекты, как было упомянуто выше, занимают достаточно маленький объем памяти, а битмап можно снова создать, использую drawInRect.
Итак, что мы получаем при этом подходе: контроллер больше не должен заполнять данными представление, после предупреждения о нехватке памяти. К примеру у нас создан viewController с несколькими текстовыми полями. При сообщении о нехватке памяти контроллер должен был бы сохранить текст в этих полях и повторно заполнить их при вызове viewDidLoad или viewWillAppear:. Больше в этом нет необходимости, поскольку текстовые поля никогда не будут уничтожены и сохранят свой текст до того момента, когда изображение будет снова отрисованно. Это упрощает часть кода, в которой возникает много ошибок.
Также есть некоторые действительно крутые фишки, встроенные в процесс отрисовки слоя. Но для начала давайте проверим, хорошо ли мы понимаем выделение (allocation) и освобождение (deallocation) памяти. Когда память выделяется (под объект, битмап, не важно) соответствующий размер блока в куче (heap) отмечается, как «используемый» и возвращается указатель (pointer) на начало этого блока. В случае выделения памяти под объект мы думаем об этом указателе, как об объекте, но на самом деле это просто указатель на адрес памяти.
Когда блок памяти «используется» есть механизм защиты, препятствующий использованию этого блока памяти не через возвращенный указатель. Поэтому с этой памятью довольно тяжело напортачить.
Освобождение (deallocation) просто снимает эту защиту и помечает блок памяти, как «не используется». Это означает, что при следующем выделении памяти может быть использован весь этот блок или его часть. При освобождении памяти, значения, хранящиеся в этом блоке не меняются. В то время как, все еще есть возможность получить к ней доступ при том, что она могла быть изменена из другого места. Поэтому никогда не безопасно получать доступ к памяти через указатель, полученный при выделении.
Теперь о том, почему это имеет значение для представлений и их слоев. Каждый слой имеет свойство содержимого (contents property), которое указывает на объект, хранящий битмап.
Это приватный объект, экземпляр класса CABackingStore, который содержит актуальный битмап, а также некоторые метаданные (такие как, есть ли у изображения alpha channel и как много байт на пиксель используется). Таким образом именно CABackingStore должен быть уничтожен, когда память заканчивается.
Однако, тонкость заключается в том, что при нехватке памяти CABackingStore (хранилище) не уничтожается. Если представление (view) не активно в момент предупреждения о нехватке памяти, то хранилище его слоя помечается флагом volatile. Этот флаг, в данном контексте, все равно, что освобождение памяти (deallocation) и дает доступ к ее использованию для различных целей. Разница между флагом и освобождением памяти (deallocation) в том, что «помеченная» память фактически может быть восстановлена, она не безвозвратно потеряна.
Рассмотрим, почему это отличная оптимизация. Если у представления (точнее слоя) было уничтожено хранилище (CABackingStore), в следующий раз представление должно его пересоздать, выполняя drawInRect:. Это ресурсоемкая операция и ее желательно избегать. Имея возможность восстановить свое хранилище слой может избежать нового вызова drawInRect:.
Конечно, есть вероятно, что между моментом, когда хранилище было помечено флагом и моментом когда требуется показать представление память была полностью израсходована на другие цели. В таком случае для создания битмапа вызова drawInRect: не избежать.
Автор: AndreyPanov