вторник, 11 ноября 2014 г.

Коротко. Об оптимизации потребления памяти под xCode. Затравочка

Пусть у нас есть некий алгоритм, который использует синглетон.

И пусть этот синглетон в свою очередь грузит какие-то данные из файла.

Примерно так:

Реализация синглетона:

@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 относится к "тем самым автоматам". Которые "опасны".

Что это означает?

Это отнюдь не означает, что "ими не надо пользоваться".

Это означает, что зачастую полезно представлять - как они устроены "в байтах".

Надеюсь, что даже то, что я написал - кому-нибудь окажется полезным.

Ну а пока - у меня всё.

Комментариев нет:

Отправить комментарий