Как уже упоминалось выше, наше приложение имеет определенный набор известных флагов функций. Сначала их можно определить так:
enum FeatureFlag : String, CaseIterable, Codable {
case allowsTrading
case allowsFundScreener
case allowsFundsTransfer
}
Достаточно просто. Но опять же, теперь любое значение, определенное с типом FeatureFlag
, может обрабатывать только один из этих указанных c известных типов.
Теперь, скажем, благодаря новой функции в бэкэнде, новый флаг allowsSavings
определяется и передается в ваше приложение. Если вы вручную не написали логи декодирования c (или не использовали дополнительные опции), декодер не сможет работать.
Но что, если вам не нужно было их писать? Что если enum может обрабатывать неизвестные случаи автоматически?
Хитрость заключается в том, чтобы определить один дополнительный случай other
со связанным значением типа String
. Этот новый регистр обрабатывает все неизвестные типы, передаваемые ему при декодировании.
Вот наш обновленный enum:
enum FeatureFlag : Codable {
case allowsTrading
case allowsFundScreener
case allowsFundsTransfer
case other(String)
}
Первая проблема связана с тем, что other
имеет ассоциированное значение, CaseIterable
больше не может быть автоматически синтезирован, поэтому мы должны вручную реализовать его самостоятельно. Давайте сделаем это здесь ...
extension FeatureFlag : CaseIterable {
typealias AllCases = [FeatureFlag]
static let allCases:AllCases = [
.allowsTrading,
.allowsFundScreener,
.allowsFundsTransfer
]
}
Вы заметите, что я специально игнорирую новый регистр other
здесь, потому что эта версия кода не знает, какое значение содержится в other
, таким образом, с нашей точки зрения, мы можем рассматривать его так, как будто его просто нет.
По той же причине, что и раньше - случай other
, имеющий ассоциированное значение - мы также должны вручную реализовать RawRepresentable
, но это действительно то, где случаются маги c!
Маги c Соус
Хитрость заключается в том, что при создании экземпляра перечисления вы сначала ищете известный тип в пределах allCases
( на основе rawValue
) и, если он найден, используйте его.
Если совпадение не найдено, используйте новый случай other
, поместив неизвестное значение внутрь.
Аналогично, на обратном пути через геттер rawValue
сначала проверьте, является ли он типом other
, и, если да, верните соответствующее значение. В противном случае верните строку, описывающую известный случай.
Вот реализация обоих:
extension FeatureFlag : RawRepresentable {
init?(rawValue: String) {
self = FeatureFlag.allCases.first{ $0.rawValue == rawValue }
??
.other(rawValue)
}
var rawValue: String {
switch self {
case let .other(value) : return value
default : return String(describing:self)
}
}
}
Вот тот же инициализатор, но с (бедным человеком) ведением журнала неизвестных значений, полезным для отладки. что на самом деле отправляет серверная часть ...
init?(rawValue: String) {
guard let knownCase = FeatureFlag.allCases.first(where: { $0.rawValue == rawValue }) else {
print("Unrecognized \(FeatureFlag.self): \(rawValue)")
self = .other(rawValue)
return
}
self = knownCase
}
Примечание: здесь я просто использую сами случаи в качестве необработанного значения. Конечно, вы можете вручную расширить дополнительные случаи, если ваши значения перечисления должны совпадать с различными значениями на сервере, например ...
var rawValue: String {
switch self {
case .allowsTrading : return "ALLOWS_TRADING"
case .allowsFundScreener : return "ALLOWS_FUND_SCREENER"
case .allowsFundsTransfer : return "ALLOWS_FUNDS_TRANSFER"
case let .other(value) : return value
}
}
Сравнения также происходят на основе необработанного значения, поэтому благодаря всем Из вышеперечисленного все три значения равны ...
let a = FeatureFlag.allowsTrading
let b = FeatureFlag(rawValue: "allowsTrading")!
let c = FeatureFlag.other("allowsTrading")
let x = a == b // x is 'true'
let y = a == c // y is 'true'
let z = b == c // z is 'true'
Кроме того, поскольку необработанное представляемое значение является строкой, которая может быть хэшируемой, вы также можете сделать это перечисление Hashable
(и, таким образом, также Equatable
), просто указав его соответствие этому протоколу.
extension FeatureFlag : Hashable {}
Теперь вы можете использовать его в наборах или в качестве ключей в словаре. Используя 'a', 'b' и 'c' сверху - опять же, все равны - вы можете использовать их так, как ...
var items = [FeatureFlag:Int]()
items[a] = 42
print(items[a] ?? -1) // prints 42
print(items[b] ?? -1) // prints 42
print(items[c] ?? -1) // prints 42
С учетом вышеизложенного вы можете Теперь закодируйте или расшифруйте любую строку в этот тип перечисления, но при этом сохраняйте доступ к известным случаям, которые вас интересуют, и все это без необходимости писать какие-либо пользовательские логики декодирования c в типах вашей модели. А когда вы «знаете» о новом типе, просто добавьте новый случай, и вы хорошо справляетесь с go!
Побочным эффектом: Кодирование / декодирование без потерь
Другой побочный эффект Преимущество этого подхода состоит в том, что он поддерживает неизвестные значения в случае other
, поэтому, если вам когда-нибудь понадобится перекодировать ваши модели, значения также будут перезаписаны через кодировщик.
Это означает, например, Если ваше старое приложение считывает модель, содержащую новый, неизвестный регистр перечисления, а затем необходимо повторно закодировать это значение, вы не потеряете данные, поскольку они сохраняются, как в известном случае, поэтому, хотя вы сами можете их игнорировать, кодер / декодер не.
Наслаждайтесь!