Пусть у нас есть некий алгоритм, который использует синглетон.
И пусть этот синглетон в свою очередь грузит какие-то данные из файла.
Примерно так:
Реализация синглетона:
Его использование:
Какие тут есть проблемы?
Пока - вроде бы никаких.
Но проблемы вообще говоря есть.
И связаны они со стратегией работы с NSData * vRestoredData.
Рассказать какие?
Намекну:
1. Пусть ресурс "DataName" - достаточно большой (ну например - несколько десятков мегабайт).
2. Пусть SomeAlgorithm - достаточно требователен к потреблению памяти (и несколько десятков мегабайт - для него - критичны).
Ну и сразу дам "правильный ответ".
Метод someData - "должен" бы выглядеть как-то так:
Ну и детали того почему этот код - "правильный" - я напишу позже.
Хотя и так - "навскидку" - понятно, что мы уменьшили время жизни объектов vPath и vRestoredData.
Ну и пост из "той же серии", но про Delphi - Сокращение времени жизни интерфейса.
Ну и ещё - Об "опасностях" ARC и прочих "автоматов". Пока коротко.
Вообще говоря autoreleasepool относится к "тем самым автоматам". Которые "опасны".
Что это означает?
Это отнюдь не означает, что "ими не надо пользоваться".
Это означает, что зачастую полезно представлять - как они устроены "в байтах".
Надеюсь, что даже то, что я написал - кому-нибудь окажется полезным.
Ну а пока - у меня всё.
И пусть этот синглетон в свою очередь грузит какие-то данные из файла.
Примерно так:
Реализация синглетона:
@interface SomeLoader: NSObject // - интерфейс класса SomeLoader - (NSDictionary *) someData; // - метод получения данных потребованию + (instancetype) instance; // - метод получения экземпляра синглетона // instancetype означает "тип текущего определяемого класса" // в данном случае - SomeLoader * // http://osxdev.ru/tag/instancetype/ // http://nshipster.com/instancetype/ @end // - конец определения интерфейса класса SomeLoader ... @implementation SomeLoader // - реализация класса SomeLoader { NSDictionary * m_someData; // - наши данные, которые грузятся по требованию } - (NSDictionary *) someData { if (!m_someData) { // - данные не созданы NSString * vPath = [[NSBundle mainBundle] pathForResource: @"DataName" ofType: @""]; // - получаем путь к ресурсу в Bundle // https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSBundle_Class/ // Путь попал попал в текущий autoreleasepool NSData * vRestoredData = [NSData dataWithContentsOfFile: vPath]; // - "мапируем" данные на файл // https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSData_Class/#//apple_ref/occ/clm/NSData/dataWithContentsOfFile: // https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSData_Class/#//apple_ref/occ/clm/NSData/dataWithContentsOfFile:options:error: // "Мапинг" попал в текущий autoreleasepool // НО на самом деле это далеко не всегда - "мапинг", // а зачастую - очень даже загрузка данных в память // Ибо: // "Return Value // A data object by reading every byte from the file specified by path. Returns nil if the data object could not be created." // - в чём часто можно убедится под отладчиком, смотря на текущий прирост памяти ДО и ПОСЛЕ вызова dataWithContentsOfFile. if (vRestoredData) { NSError * vError = nil; // - сюда помещается ошибка NSPropertyListFormat vFormat; // - сюда помещается формат считанных данных m_someData = [ [NSPropertyListSerialization propertyListWithData: vRestoredData // - "мапинг", откуда десериализуется объект options: NSPropertyListImmutable // - заявляем о том, // что мы НЕ ХОТИМ менять десериализуемый объект // если объект РЕАЛЬНО не надо менять, // то он так оптимальнее создаётся format: &vFormat // - сюда помещается формат считанных данных error: &vError // - сюда помещается ошибка // - десериализуем данные в m_someData ] retain // - захватываем их в m_someData ]; // Данные попали в текущий autoreleasepool и в m_someData assert(!vError); // - ошибок десериализации - нет } } return m_someData; // - возвращаем данные } - (void) dealloc { DESTROY(m_someData); // - освобождаем наши данные [super dealloc]; } + (instancetype) instance // - метод получения экземпляра синглетона { static SomeLoader * g_Loader = nil; // - место хранения экземпляра нашего синглетона static dispatch_once_t g_onceToken; dispatch_once(&onceToken, // - вызов метода из GCD, который гарантирует, что в блок мы попадём только ОДИН раз // Подробности например читаем тут: // http://stackoverflow.com/questions/5720029/create-singleton-using-gcds-dispatch-once-in-objective-c ^{ // - это собственно вызываемый блок g_Loader = [self new]; // - создаём экземпляр синглетона // Где он потом освобождается - это отдельный вопрос. // Пока для простоты - нигде. } // - конец вызываемого блока ); // - конец вызова dispatch_once return g_Loader; // - возвращаем экземпляр нашего синглетона, // который создан ГАРАНТИРОВАННО только ОДИН РАЗ } @end // - конец определения реализации класса SomeLoader ...
Его использование:
@interface SomeAlgorithm + (void) doWith: (NSDictionary *) aData; @end ... @interface SomeClass - (void) SomeMethod; @end ... @implementation SomeClass - (void) SomeMethod { @autoreleasepool { // Тут начинается новый пул объектов. // https://developer.apple.com/library/ios/documentation/cocoa/conceptual/memorymgmt/Articles/mmAutoreleasePools.html ... NSDictionary *vData = [ [SomeLoader instance] // - получаем синглетон someData // и получаем его данные ]; [SomeAlgorithm doWith: vData]; // - обрабатываем данные } // - именно тут освобождаются объекты из autoreleasepool } @end
Какие тут есть проблемы?
Пока - вроде бы никаких.
Но проблемы вообще говоря есть.
И связаны они со стратегией работы с NSData * vRestoredData.
Рассказать какие?
Намекну:
1. Пусть ресурс "DataName" - достаточно большой (ну например - несколько десятков мегабайт).
2. Пусть SomeAlgorithm - достаточно требователен к потреблению памяти (и несколько десятков мегабайт - для него - критичны).
Ну и сразу дам "правильный ответ".
Метод someData - "должен" бы выглядеть как-то так:
- (NSDictionary *) someData { if (!m_someData) { // - данные не созданы @autoreleasepool { // - создаём новый autoreleasepool NSString * vPath = [[NSBundle mainBundle] pathForResource: @"DataName" ofType: @""]; // - получаем путь к ресурсу // Путь попал попал в текущий autoreleasepool NSData * vRestoredData = [NSData dataWithContentsOfFile: vPath]; // - "мапируем" данные на файл // "Мапинг" попал в текущий autoreleasepool if (vRestoredData) { // Мапинг - создался NSError * vError = nil; // - сюда помещается ошибка NSPropertyListFormat vFormat; // - сюда помещается формат считанных данных m_someData = [ [NSPropertyListSerialization propertyListWithData: vRestoredData options: NSPropertyListImmutable format: &vFormat error: &vError // - десериализуем данные в m_someData ] retain // - захватываем их в m_someData ]; // Данные попали в текущий autoreleasepool и в m_someData assert(!vError); // - ошибок десериализации - нет } } // @autoreleasepool // - тут освободятся объекты из пула // В частности - vPath, vRestoredData и m_someData // При этом vPath, vRestoredData - уничтожатся. // А вот m_someData - не уничтожится, потому, что мы его захватили при помощи retain } // if (!m_someData) return m_someData; // - возвращаем данные }
Ну и детали того почему этот код - "правильный" - я напишу позже.
Хотя и так - "навскидку" - понятно, что мы уменьшили время жизни объектов vPath и vRestoredData.
Ну и пост из "той же серии", но про Delphi - Сокращение времени жизни интерфейса.
Ну и ещё - Об "опасностях" ARC и прочих "автоматов". Пока коротко.
Вообще говоря autoreleasepool относится к "тем самым автоматам". Которые "опасны".
Что это означает?
Это отнюдь не означает, что "ими не надо пользоваться".
Это означает, что зачастую полезно представлять - как они устроены "в байтах".
Надеюсь, что даже то, что я написал - кому-нибудь окажется полезным.
Ну а пока - у меня всё.
Комментариев нет:
Отправить комментарий