Нечетное поведение при выселении NSCache - PullRequest
2 голосов
/ 04 апреля 2011

Я использую NSCache для хранения элементов, которые следует кэшировать по соображениям производительности, поскольку их воссоздание довольно дорого. Понимание поведения NSCache вызывает у меня некоторые головные боли.

Я инициализирую свой NSCache следующим образом:

   _cellCache = [[NSCache alloc] init];
   [_cellCache setDelegate:self];
   [_cellCache setEvictsObjectsWithDiscardedContent:NO];   // never evict cells

Объекты, хранящиеся в кэше, реализуют протокол NSDiscardableContent. Теперь моя проблема в том, что класс NSCache, кажется, не работает правильно в нескольких случаях.

1) Сначала небольшая проблема. SetCountLimit NSCache: утверждает, что:

Установка ограничения количества на число, меньшее или равное 0, не повлияет на максимальный размер кэша.

Может кто-то пролить свет на то, что это значит? То есть, что NSCache максимален и будет выдавать сообщения discardContentIfPossible только тогда, когда в другом месте требуется память? Или, что NSCache минимален, и он немедленно выдаст discardContentIfPossible сообщений?

Первое имеет больше смысла, но, похоже, тестирование указывает на то, что происходит позже. Если я регистрирую вызовы метода discardContentIfPossible в моем кэшированном объекте, я вижу, что он вызывается почти сразу - после добавления в кэш только 2-3 десятков элементов (каждый менее 0,5 МБ ).

Хорошо. Поэтому я пытаюсь установить предел большого количества - гораздо больше, чем мне когда-либо понадобится, добавив следующую строку:

   [_cellCache setCountLimit:10000000];

Тогда discardContentIfPossible сообщения больше не отправляются почти сразу. Я должен загрузить намного больше контента в кеш и использовать его некоторое время, прежде чем эти сообщения начнут появляться, что имеет больше смысла.

Так каково здесь предполагаемое поведение?

2) Большая проблема. В документации говорится:

По умолчанию объекты NSDiscardableContent в кэше автоматически удаляются из кэша, если их содержимое отбрасывается, хотя эту политику автоматического удаления можно изменить. Если объект NSDiscardableContent помещен в кеш, кеш вызывает discardContentIfPossible после его удаления.

Поэтому я установил политику выселения на NO (как указано выше), чтобы объекты никогда не выселялись. Вместо этого, когда вызывается discardContentIfPossible , я очищаю и освобождаю внутренние данные кэшированного объекта в соответствии со специальными критериями. То есть я могу принять решение не очищать и не сбрасывать данные при определенных обстоятельствах (например, если элемент использовался совсем недавно). В таком случае я просто возвращаюсь из метода discardContentIfPossible , ничего не отбрасывая. Идея состоит в том, что какой-то другой объект, который недавно не использовался, получит сообщение в какой-то момент и вместо этого может отказаться от его содержимого.

Теперь, что интересно, все, кажется, прекрасно работает некоторое время интенсивного использования. Загрузка большого количества контента, размещение и доступ к объектам в кеше и т. Д. Однако через некоторое время, когда я пытаюсь получить доступ к произвольному объекту в NSCache, его буквально нет! Каким-то образом кажется, что он был удален - хотя я специально установил политику выселения на NO.

Хорошо. Таким образом, реализация метода делегата cache: willEvictObject: показывает, что он никогда не вызывается. Что означает, что объект на самом деле не выселяют. Но он таинственным образом исчезает из NSCache, поскольку его нельзя найти в будущих поисках - или каким-то образом ключ, с которым он был связан, больше не отображается на исходный объект. Но как это может произойти?

Кстати, ключевой объект, который я связываю с моими объектами значений (CALayer), является контейнером / оболочкой NSURL, поскольку я не могу использовать NSURL напрямую, поскольку NSCache не копирует ключи - только сохраняет их, и более поздний ключ NSURL, используемый для поиска, может не совпадать с исходным объектом точный (только та же строка URL), который изначально использовался для загрузки кэша с этим объектом. Принимая во внимание, что с помощью оболочки / контейнера NSURL я могу гарантировать, что это именно тот объект исходного ключа, который использовался для добавления объекта исходного значения в NSCache.

Может кто-нибудь пролить свет на это?

1 Ответ

13 голосов
/ 04 апреля 2011

Ваш комментарий "никогда не выселять клетки" - это не то, что -setEvictsObjectsWithDiscardedContent: говорит, что делает. Он говорит, что не будет выселять клетки только потому, что их содержимое было отброшено. Это не то же самое, что сказать, что никогда не будет выселять клетки. Вы по-прежнему можете бежать за максимальный размер или количество, и они все еще могут быть выселены. Преимущество отбрасываемого контента состоит в том, что вы можете отказаться от части своего контента, не удаляя себя из кэша. Затем вы бы перестроили удаленный контент по требованию, но, возможно, вам не пришлось бы перестраивать весь объект. В некоторых случаях это может быть победой.

Это подводит нас к первому вопросу. Да, NSCache начинает выселяться, когда достигает максимального размера. Вот почему это называется «максимальный размер». Вы не указываете, Mac это или iPhone, но в обоих случаях вы не хотите, чтобы кэш увеличивался до тех пор, пока не будет исчерпана память. Это плохо по ряду направлений. На Mac вы начнете менять местами задолго до исчерпания памяти. В iOS вы не хотите начинать отправлять предупреждения о памяти всем другим процессам только потому, что один процесс сошел с ума со своим кэшем. В любом случае слишком большой кеш обеспечивает низкую производительность. Поэтому объекты, помещенные в NSCache, всегда должны быть выселены в любое время.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...