How to display OptionSet values in human-readable form?

后端 未结 5 1478
别跟我提以往
别跟我提以往 2021-02-13 21:17

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

相关标签:
5条回答
  • 2021-02-13 21:21

    This is how I did it.

    public struct Toppings: OptionSet {
            public let rawValue: Int
            
            public static let cheese = Toppings(rawValue: 1 << 0)
            public static let onion = Toppings(rawValue: 1 << 1)
            public static let lettuce = Toppings(rawValue: 1 << 2)
            public static let pickles = Toppings(rawValue: 1 << 3)
            public static let tomatoes = Toppings(rawValue: 1 << 4)
            
            public init(rawValue: Int) {
                self.rawValue = rawValue
            }
        }
        
        extension Toppings: CustomStringConvertible {
            
            static public var debugDescriptions: [(Self, String)] = [
                (.cheese, "cheese"),
                (.onion, "onion"),
                (.lettuce, "lettuce"),
                (.pickles, "pickles"),
                (.tomatoes, "tomatoes")
            ]
            
            public var description: String {
                let result: [String] = Self.debugDescriptions.filter { contains($0.0) }.map { $0.1 }
                let printable = result.joined(separator: ", ")
                
                return "\(printable)"
            }
        }
    
    0 讨论(0)
  • 2021-02-13 21:27
    struct MyOptionSet: OptionSet {
        let rawValue: UInt
        static let healthcare   = MyOptionSet(rawValue: 1 << 0)
        static let worldPeace   = MyOptionSet(rawValue: 1 << 1)
        static let fixClimate   = MyOptionSet(rawValue: 1 << 2)
        static let exploreSpace = MyOptionSet(rawValue: 1 << 3)
    }
    
    extension MyOptionSet: CustomStringConvertible {
        static var debugDescriptions: [(Self, String)] = [
            (.healthcare, "healthcare"),
            (.worldPeace, "world peace"),
            (.fixClimate, "fix the climate"),
            (.exploreSpace, "explore space")
        ]
    
        var description: String {
            let result: [String] = Self.debugDescriptions.filter { contains($0.0) }.map { $0.1 }
            return "MyOptionSet(rawValue: \(self.rawValue)) \(result)"
        }
    }
    

    Usage

    var myOptionSet: MyOptionSet = []
    myOptionSet.insert(.healthcare)
    print("here is my options: \(myOptionSet)")
    
    0 讨论(0)
  • 2021-02-13 21:33

    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"]
    
    0 讨论(0)
  • 2021-02-13 21:38

    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
    
    0 讨论(0)
  • 2021-02-13 21:47

    This article in NSHipster gives an alternative to OptionSet that offers all the features of an OptionSet, plus easy logging:

    https://nshipster.com/optionset/

    If you simply add a requirement that the Option type be CustomStringConvertible, you can log Sets of this type very cleanly. Below is the code from the NSHipster site - the only change being the addition of CustomStringConvertible conformance to the Option class

    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
        }
    }
    

    Then using it:

    let toppings: Set<Topping> = [.onions, .bacon]
    
    print("toppings = \(toppings), rawValue = \(toppings.rawValue)")
    

    That outputs

    toppings = [.onions, .bacon], rawValue = 6

    Just like you want it to.

    That works because a Set displays its members as a comma-delimited list inside square brackets, and uses the description property of each set member to display that member. The description property simply displays each item (the enum's name as a String) with a . prefix

    And since the rawValue of a Set<Option> is the same as an OptionSet with the same list of values, you can convert between them readily.

    I wish Swift would just make this a native language feature for OptionSets.

    0 讨论(0)
提交回复
热议问题