Расширенное кодирование и декодирование
Протоколы Codable
- одно из самых крутых недавних дополнений к Swift. Несмотря на то, что он работает аналогично своим аналогам из сообщества, таким как Unbox
, Codable
имеет то преимущество, что работает на компиляторе.
Одной из моих любимых функций в Unbox
было предоставление контекста операции декодирования. Давайте посмотрим, как добиться того же с помощью Codable
.
Пример 1: присвоение разных значений одному и тому же свойству
Декодирование с контекстом означает возможность изменять способ декодирования типов в зависимости от того, что происходит в вашем приложении. Допустим, наше приложение отправляет запрос с просьбой предоставить информацию о заголовке, который должен отображаться на экране, при этом наше приложение локально сохраняет в модели дополнительное свойство textColor
:
Теперь представьте себе следующую ситуацию: что, если мы хотим, чтобы textColor
отличался в зависимости от того, на каком экране отображается такой заголовок? Например, заголовки на главном экране приложения могут иметь зеленый текст, а заголовки на экране профиля могут быть синими.
Есть несколько способов добиться этого, но давайте сосредоточимся на Codable
. Codable
позволяет вам предоставлять контекст для _10 _ / _ 11_ через их свойство userInfo
:
/// Contextual user-provided information for use during decoding.
open var userInfo: [CodingUserInfoKey : Any]
CodingUserInfoKey
- это RawRepresentable
строковое перечисление, поэтому вы можете добавить что угодно к userInfo
. Когда это будет сделано, те же значения userInfo
будут доступны как часть внутреннего экземпляра _19 _ / _ 20_:
Чтобы это сработало, просто заполните userInfo
словарь перед декодированием данных.
let decoder = JSONDecoder()
decoder.userInfo[HeaderInformation.textColorUserInfoKey] = UIColor.red
let headerInfo = decoder.decode(HeaderInformation.self, from: data)
CodingUserInfoKey
требует принудительного развертывания, потому что это RawRepresentable
, но это никогда не приведет к сбою. Чтобы скрыть это, я предпочитаю абстрагировать его внутри расширения, которое вместо этого принимает обычный String
:
Теперь мы можем использовать собственный CodingKeys
типа в качестве userInfo
ключа, что привело к следующим улучшениям:
let key = HeaderInformation.CodingKeys.textColor.stringValue
decoder.set(context: UIColor.red, forKey: key)
//
textColor = decoder.getContext(forKey: CodingKeys.textColor.stringValue)
Пример 2: Работа с модульными структурами с стиранием текста
Отличный пример того, как я использую Codable
контексты, - это то, как AnyRoute
работает в библиотеке RouterService:
Короче говоря, у нас есть следующая среда:
- Приложения могут определять свои собственные
Routes
, которые представляют собой структуры, используемые для перехода между экранами. - Эти
Routes
зарегистрированы в экземпляреRouterService
, который получает указанноеRoutes
и подталкивает соответствующие контроллеры представления. Routes
может быть декодирован из определенного строкового формата, что позволяет серверной части определять, на какой экран приложение должно перейти.
Последнее осуществляется с помощью AnyRoute
- стертого Route
типа, который знает, как декодировать такой строковый формат в фактический Route
тип, определенный приложением. Но есть проблема: поскольку AnyRoute
определен внутри библиотеки RouterService
, у него нет доступа к Routes
приложения. Как он может декодировать правильный тип Route
?
Это может быть достигнуто с помощью контекстных функций Codable
. Поскольку Routes
должен быть зарегистрирован в основном экземпляре RouterService
, мы можем ввести его в операцию декодирования и заставить его определять, какой Route
должен быть декодирован:
Предполагая, что RouterService
был введен в JSONDecoder
, операция декодирования будет отложена до него:
Это децентрализованное поведение особенно важно для модульных приложений (что является вариантом использования RouterService
), поскольку разные цели могут ссылаться и декодировать AnyRoutes
, не имея доступа к реальным Routes
приложениям.