Расширенное кодирование и декодирование

Протоколы 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 приложениям.