Болезненный путь с вытеснением URLCache и созданием подклассов

06 февраля 2023

 

Класс URLCache реализует кэширование ответов на запросы загрузки URL-адресов путём сопоставления объектов NSURLRequest с объектами CachedURLResponse. Он обеспечивает составной кэш в памяти и на диске и позволяет управлять размерами как в памяти, так и на диске. Вы также можете контролировать путь, по которому постоянно хранятся данные кэша.

Если вы работали с загрузкой URL-адресов в iOS, вы, вероятно, знакомы с классом URLCache. Вы можете создать экземпляр, определить пределы памяти и ёмкости диска, установить его в свой объект URLSessionConfiguration, а затем начать создавать классы URLSessions, которые будут использовать этот кэш для хранения записей в памяти и на диске. Он существует с iOS 2.0 (задолго до появления URLSession).

Однако что происходит, когда вы достигаете (максимальной) ёмкости диска для вашего URLCache? Компания Apple не задокументировала какой-либо конкретной стратегии вытеснения. Я давно предполагал, что это будет своего рода метод LRU (Least Recently Used — это алгоритм, при котором вытесняются значения, которые дольше всего не запрашивались). Однако, похоже, это не так. Основываясь на моём собственном тестировании на iOS 16, кажется, что URLCache вытесняет все записи, когда заканчивается ёмкость диска. Похоже, это происходит во время записи ответов, поэтому ваше приложение может работать на переднем плане, пока URLCache удаляет свои записи.

Кажется, что URLCache вытесняет все записи при достижении ёмкости диска.

Стратегия “всегда вытеснять все записи” не подходила для моего варианта использования, поэтому я решил написать замену URLCache, которая бы реализовывала собственное хранилище и обеспечивала бы стратегию вытеснения LRU. Apple специально упоминает этот вариант использования для создания подклассов:

Класс URLCache предназначен для использования “как есть”, но вы можете создать его подкласс, если у вас есть особые потребности. Например, вам необходимо проверить, какие ответы кэшируются, или повторно реализовать механизм хранения из соображений безопасности или по другим причинам.

Однако я сразу же столкнулся с проблемами при попытке использовать мой подкласс URLCache с сетью URLSession. Таким образом, загрузка URL-адресов больше не использовала кэшированные ответы для удовлетворения каких-либо запросов, даже несмотря на то, что кэшированные ответы, предоставленные моим подклассом URLCache, были идентичны ответам, предоставленным стандартным суперклассом URLCache.

Я был уверен, что что-то упустил, поэтому задокументировал свой тестовый пример и задал вопрос на форумах разработчиков Stack Overflow и Apple Developer Forums.

Менее чем через 24 часа я получил ответ от Quinn "The Eskimo":

Мой опыт показывает, что создание подклассов системных классов загрузки URL-адресов Foundation является болезненным решением [1]. Если бы я был на вашем месте, я бы сделал ваше собственное кеширование над системным уровнем загрузки URL-адресов Foundation.

Делитесь и наслаждайтесь
Quinn "The Eskimo" @ Developer Technical Support @ Apple

[1] Когда-то система загрузки URL-адресов Foundation была реализована на Objective-C и существовала в рамках Foundation. В том мире в основном работали подклассы. Вскоре после этого (здесь я говорю до введения NSURLSession) основная реализация сменила язык и перешла на CFNetwork. С тех пор классы, которые вы видите в Foundation, в основном представляют собой тонкие оболочки вокруг (приватных) типов CFNetwork. В целом это нормально, за исключением влияния на подклассы.

Поэтому я решил написать этот пост в блоге, чтобы привлечь внимание к этой проблеме. Надеюсь, это сэкономит ваше время, если вы думаете о подклассе URLCache.

Если вам необходимо управлять стратегией вытеснения или реализовать собственное хранилище, вам придется выполнять пользовательское кэширование полностью вне загрузки Foundation URL.

Оригинал статьи

Содержание