MemCache для iOS

Предистория появления класса MemCache тривиальна. Есть в разработке проект который большую часть времени занимается подгрузкой небольших объемов данных из сети. В основном JSON данные и небольшие изображения. В каждом контроллере был объявлен NSMutableDictionary в котором и сохранялись результаты запросов. Но с ростом количества контроллеров возникло две проблемы — дублирование кода и потеря результатов кеширования при вызове popViewController.

Под катом решение этих проблем.

Было решено провести рефакторинг по проекту в результате которого родился класс-синглтон MemCache и две категории, по одной для NSString и NSObject.

Класс MemCache должен был выполнять кэширование на небольшое время (в этом проекте было достаточно двух минут), самостоятельно очищать память при возникновении MemoryWarning, а также при переходе приложения в фон.

Ссылка на исходники в конце статьи, а пока расскажу про основные этапы реализации.

В методе init подписываемся на уведомления о memory warnings и потере активности.
- (id) init
{
	self = [super init];
	if (self != nil) 
	{        
        NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
        [center addObserver:self
                   selector:@selector(clearCache)
                       name:UIApplicationDidReceiveMemoryWarningNotification
                     object:nil];
        [center addObserver:self
                   selector:@selector(clearCache)
                       name:UIApplicationWillResignActiveNotification
                     object:nil];
        _lifeTime = 60.0f;
	}
	return self;
}

Не забываем отписаться от уведомлений в dealloc.
- (void)dealloc
{
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center removeObserver:self 
                      name:UIApplicationDidReceiveMemoryWarningNotification 
                    object:nil];
    [center removeObserver:self 
                      name:UIApplicationWillResignActiveNotification 
                    object:nil];
}

Так как кэш может быть очищен в любой момент времени по запросу или по наступлении описанных выше событий, при записи нового элемента нужно проверять, а есть ли собственно куда писать. Для каждого элемента при добавлении запускаем отложенный вызов метода удаления его из кэша.
- (void)setValue:(id)value forKey:(NSString *)key
{
    if (!_cache)
    {
        _cache = [[NSMutableDictionary alloc] init];
    }
    
    [_cache setValue:value forKey:key];
    [NSObject cancelPreviousPerformRequestsWithTarget:_cache selector:@selector(removeObjectForKey:) object:key];
    [_cache performSelector:@selector(removeObjectForKey:) withObject:key afterDelay:_lifeTime];
}

Для удобства использования использовал категории. Теперь чтобы положить объект в кэш, достаточно написать что-то вроде:
[jsonValue setMemCacheValueForKey:[[[connection originalRequest] URL] absoluteString]];

А достать объект можно так:
id val = [[url absoluteString] memCacheValue];

Ссылка на репозиторий — github.com/eltiren/MemCahce-IOS

P.S. Компилировал с включенным ARC.

Комментарии 11

    0
    отлично, удобно!
      +1
      Мне кажется лучше было бы сделать кэширование на диске с определенным сроком жизни кэша и с возможностью принудительного его обновления, как всего целиком, так и каждой кэшируемой единицы в отдельности (как кукисы например).
      Скорость доступа к данным на диске на два порядка а то и на все три быстрее сетевого доступа, поэтому тут преимущество хранения данных в памяти над хранением на диске практически небольшое. Но засорять память кэшем, который непонятно когда потребуется — помоему не очень оптимально.

      На мобильных системах всегда нужно думать о балансе оптимальности/размера и производительности.
        +1
        Данные из кэша автоматом удаляются, если в течение 2 минут к ним не было доступа. Опять же «диск» имеет конечное количество циклов записи.
          +1
          Получается очень маложивущий кэш. Опять таки не очень универсальное решение.
          По поводу циклов перезаписи, тут я не согласен. Проблема не актуальная. Я не видел ни одного еще примера, когда законился бы ресурс флэша на телефоне.
          Даже за 4 года активного использования моего iPhone первого поколения такой проблемы не замечено. Так что тут сроки старения технологически намного меньше чем срок жизни диска.
          Но это мое субьективное мнение.
          В любом случае, не отрицаю, что это одно из возможных решений, а те недотатки, что я высказал — это мое видение решения этой задачи.
        +1
        Почему плохо отписываться от уведомлений вот так [center removeObserver:self]; link
          0
          В отношении к UIViewController — полностью согласен с автором статьи по ссылке. Но я использую отписывание от всех нотификаций в деаллоке синглтона, который будет вызван при окончании работы приложения, так что описанных проблем не возникнет. Но для чистоты кода исправил и перезалил на гитхаб.
          0
          Переменная _cache не релизится в dealloc
            0
            > P.S. Компилировал с включенным ARC.
            0
            Почему не использовать стандартные вещи из NSURLRequestCachePolicy? Свои велосипеды лучше? или была потребность с кеше, который по сути кешем является в ваших собственных терминах?
              0
              Была необходимость в кеше именно таким поведением.
                0
                Маловато гибкости в cache policy. Даже нет возможности задать время жизни кеша.

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое