Пусть у нас есть некий алгоритм, который использует синглетон.
И пусть этот синглетон в свою очередь грузит какие-то данные из файла.
Примерно так:
Реализация синглетона:
Его использование:
Какие тут есть проблемы?
Пока - вроде бы никаких.
Но проблемы вообще говоря есть.
И связаны они со стратегией работы с 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 относится к "тем самым автоматам". Которые "опасны".
Что это означает?
Это отнюдь не означает, что "ими не надо пользоваться".
Это означает, что зачастую полезно представлять - как они устроены "в байтах".
Надеюсь, что даже то, что я написал - кому-нибудь окажется полезным.
Ну а пока - у меня всё.
Комментариев нет:
Отправить комментарий