Как отображать значения OptionSet в удобочитаемой форме?
Swift имеет тип OptionSet, который в основном добавляет операции набора к битам бит C-Style. Apple использует их довольно широко в своих рамках. Примеры включают параметр параметров в animate(withDuration:delay:options:animations:completion:)
.
На стороне плюса он позволяет использовать чистый код, например:
options: [.allowAnimatedContent, .curveEaseIn]
Однако есть и недостатки.
Если я хочу отобразить указанные значения OptionSet
, похоже, нет чистого способа сделать это:
let options: UIViewAnimationOptions = [.allowAnimatedContent, .curveEaseIn]
print("options = " + String(describing: options))
Отображает очень бесполезное сообщение:
options = UIViewAnimationOptions (rawValue: 65664)
Документы для некоторых из этих битовых полей выражают константу как значение силы двух:
flag0 = Flags(rawValue: 1 << 0)
Но документы для моего примера OptionSet, UIViewAnimationOptions
, ничего вам не говорят о числовом значении этих флагов и вычислении битов из десятичных чисел не просто.
Вопрос:
Есть ли какой-то чистый способ сопоставления OptionSet с выбранными значениями?
Мой желаемый результат будет примерно таким:
options = UIViewAnimationOptions ([. allowAnimatedContent,.curveEaseIn])
Но я не могу придумать способ сделать это без кучи беспорядочного кода, который потребовал бы, чтобы я поддерживал таблицу отображаемых имен для каждого флага.
(Я заинтересован в том, чтобы делать это как для системных фреймворков, так и для специальных наборов параметров, которые я создаю в своем собственном коде.)
Перечисления позволяют вам иметь как имя, так и необработанное значение для перечисления, но они не поддерживают функции набора, которые вы получаете с помощью OptionSets.
Ответы
Ответ 1
Эта статья в NSHipster дает альтернативу OptionSet, которая предлагает все функции OptionSet, а также простую регистрацию:
https://nshipster.com/optionset/
Если вы просто добавите требование, чтобы тип Option был CustomStringConvertible, вы можете очень аккуратно регистрировать наборы этого типа. Ниже приведен код с сайта NSHipster - единственное изменение заключается в добавлении соответствия CustomStringConvertible
к классу Option
protocol Option: RawRepresentable, Hashable, CaseIterable, CustomStringConvertible {}
enum Topping: String, Option {
case pepperoni, onions, bacon,
extraCheese, greenPeppers, pineapple
//I added this computed property to make the class conform to CustomStringConvertible
var description: String {
return ".\(self.rawValue)"
}
}
extension Set where Element == Topping {
static var meatLovers: Set<Topping> {
return [.pepperoni, .bacon]
}
static var hawaiian: Set<Topping> {
return [.pineapple, .bacon]
}
static var all: Set<Topping> {
return Set(Element.allCases)
}
}
typealias Toppings = Set<Topping>
extension Set where Element: Option {
var rawValue: Int {
var rawValue = 0
for (index, element) in Element.allCases.enumerated() {
if self.contains(element) {
rawValue |= (1 << index)
}
}
return rawValue
}
}
Тогда используя это:
let toppings: Set<Topping> = [.onions, .bacon]
print("toppings = \(toppings), rawValue = \(toppings.rawValue)")
Что выводит
toppings = [.onions,.bacon], rawValue = 6
Так же, как вы этого хотите.
Это работает, потому что набор отображает своих членов в виде списка, разделенного запятыми, в квадратных скобках и использует свойство description
каждого члена набора для отображения этого члена. Свойство description
просто отображает каждый элемент (имя перечисления в виде String
) с .
префикс
А поскольку rawValue для Set<Option>
совпадает с OptionSet с тем же списком значений, вы можете легко конвертировать между ними.
Я хотел бы, чтобы Swift просто сделал эту функцию родным языком для OptionSet
s.
Ответ 2
Вот один из подходов, который я выбрал, используя словарь и перебирая ключи. Не отлично, но это работает.
struct MyOptionSet: OptionSet, Hashable, CustomStringConvertible {
let rawValue: Int
static let zero = MyOptionSet(rawValue: 1 << 0)
static let one = MyOptionSet(rawValue: 1 << 1)
static let two = MyOptionSet(rawValue: 1 << 2)
static let three = MyOptionSet(rawValue: 1 << 3)
var hashValue: Int {
return self.rawValue
}
static var debugDescriptions: [MyOptionSet:String] = {
var descriptions = [MyOptionSet:String]()
descriptions[.zero] = "zero"
descriptions[.one] = "one"
descriptions[.two] = "two"
descriptions[.three] = "three"
return descriptions
}()
public var description: String {
var result = [String]()
for key in MyOptionSet.debugDescriptions.keys {
guard self.contains(key),
let description = MyOptionSet.debugDescriptions[key]
else { continue }
result.append(description)
}
return "MyOptionSet(rawValue: \(self.rawValue)) \(result)"
}
}
let myOptionSet = MyOptionSet([.zero, .one, .two])
// prints MyOptionSet(rawValue: 7) ["two", "one", "zero"]
Ответ 3
Протокол StrOptionSet:
- Добавьте свойство набора меток, чтобы проверить каждое значение метки в Self.
Расширение StrOptionSet:
- Отфильтруйте, который не пересекается.
- Вернуть текст метки в виде массива.
- Присоединяется к "," как CustomStringConvertible :: description
Вот фрагмент кода:
protocol StrOptionSet : OptionSet, CustomStringConvertible {
typealias Label = (Self, String)
static var labels: [Label] { get }
}
extension StrOptionSet {
var strs: [String] { return Self.labels
.filter{ (label: Label) in self.intersection(label.0).isEmpty == false }
.map{ (label: Label) in label.1 }
}
public var description: String { return strs.joined(separator: ",") }
}
Добавить набор меток для целевого набора параметров VTDecodeInfoFlags.
extension VTDecodeInfoFlags : StrOptionSet {
static var labels: [Label] { return [
(.asynchronous, "asynchronous"),
(.frameDropped, "frameDropped"),
(.imageBufferModifiable, "imageBufferModifiable")
]}
}
Используйте его
let flags: VTDecodeInfoFlags = [.asynchronous, .frameDropped]
print("flags: ", flags)