Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to display OptionSet values in human-readable form?

Swift has the OptionSet type, which basically adds set operations to C-Style bit flags. Apple is using them pretty extensively in their frameworks. Examples include the options parameter in animate(withDuration:delay:options:animations:completion:).

On the plus side, it lets you use clean code like:

options: [.allowAnimatedContent, .curveEaseIn]

However, there is a downside as well.

If I want to display the specified values of an OptionSet, there doesn't seem to be a clean way to do it:

let options: UIViewAnimationOptions = [.allowAnimatedContent, .curveEaseIn]
print("options = " + String(describing: options))

Displays the very unhelpful message:

options = UIViewAnimationOptions(rawValue: 65664)

The docs for some of these bit fields expresses the constant as a power-of-two value:

flag0    = Flags(rawValue: 1 << 0)

But the docs for my example OptionSet, UIViewAnimationOptions, doesn't tell you anything about the numeric value of these flags and figuring out bits from decimal numbers is not straightforward.

Question:

Is there some clean way to map an OptionSet to the selected values?

My desired output would be something like:

options = UIViewAnimationOptions([.allowAnimatedContent, .curveEaseIn])

But I can't think of a way to do this without adding messy code that would require me to maintain a table of display names for each flag.

(I'm interested in doing this for both system frameworks and custom OptionSets I create in my own code.)

Enums let you have both a name and a raw value for the enum, but those don't support the set functions you get with OptionSets.

like image 673
Duncan C Avatar asked Mar 03 '17 21:03

Duncan C


2 Answers

Here is one approach I've taken, using a dictionary and iterating over the keys. Not great, but it works.

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"]
like image 155
MH175 Avatar answered Sep 23 '22 18:09

MH175


StrOptionSet Protocol:

  • Add a labels set property to test each label value on Self.

StrOptionSet Extension:

  • Filter out which is not intersected.
  • Return the label text as array.
  • Joined with "," as CustomStringConvertible::description

Here is the snippet:

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: ",") }
}

Add the label set for target option set VTDecodeInfoFlags.

extension VTDecodeInfoFlags : StrOptionSet {
    static var labels: [Label] { return [
        (.asynchronous, "asynchronous"),
        (.frameDropped, "frameDropped"),
        (.imageBufferModifiable, "imageBufferModifiable")
    ]}
}

Use it

let flags: VTDecodeInfoFlags = [.asynchronous, .frameDropped]
print("flags:", flags) // output: flags: .asynchronous,frameDropped
like image 23
Chen OT Avatar answered Sep 23 '22 18:09

Chen OT