【问题标题】:How do you enumerate OptionSetType in Swift?如何在 Swift 中枚举 OptionSetType?
【发布时间】:2015-11-13 04:36:21
【问题描述】:

我在 Swift 中有一个自定义的 OptionSetType 结构。如何枚举实例的所有值?

这是我的 OptionSetType:

struct WeekdaySet: OptionSetType {
    let rawValue: UInt8

    init(rawValue: UInt8) {
        self.rawValue = rawValue
    }

    static let Sunday        = WeekdaySet(rawValue: 1 << 0)
    static let Monday        = WeekdaySet(rawValue: 1 << 1)
    static let Tuesday       = WeekdaySet(rawValue: 1 << 2)
    static let Wednesday     = WeekdaySet(rawValue: 1 << 3)
    static let Thursday      = WeekdaySet(rawValue: 1 << 4)
    static let Friday        = WeekdaySet(rawValue: 1 << 5)
    static let Saturday      = WeekdaySet(rawValue: 1 << 6)
}

我想要这样的东西:

let weekdays: WeekdaySet = [.Monday, .Tuesday]
for weekday in weekdays {
    // Do something with weekday
}

【问题讨论】:

  • @MartinR 在实现方面有一个很好的答案,但是对于任何看到这种情况的人来说,在用例方面都有一些事情需要考虑。请记住,OptionSets 不像enums 那样是受限制的值集,因此枚举任意集合可能会给您(根据此示例)[.Monday, .Friday, WeekdaySet(rawValue: 128)] 的结果。始终清理您的输入!
  • 虽然有一些很好的答案,但请注意,将OptionSet 用于类似的数据类型并不是一个好主意。选项集主要用于选项掩码,因为它们是由整数位定义的。更好的解决方案是将普通的enumSet 结合使用。
  • 什么是“选项掩码”?

标签: swift swift2 swift3


【解决方案1】:

从 Swift 4 开始,标准库中没有方法 枚举 OptionSetType (Swift 2) 的元素。 OptionSet(斯威夫特 3、4)。

这是一个可能的实现,它只检查每个位 底层原始值,对于每个设置的位, 返回相应的元素。 “溢出乘法”&amp;* 2 用作左移,因为 &lt;&lt; 仅针对具体整数类型定义,而不针对 IntegerType 协议。

Swift 2.2:

public extension OptionSetType where RawValue : IntegerType {

    func elements() -> AnySequence<Self> {
        var remainingBits = self.rawValue
        var bitMask: RawValue = 1
        return AnySequence {
            return AnyGenerator {
                while remainingBits != 0 {
                    defer { bitMask = bitMask &* 2 }
                    if remainingBits & bitMask != 0 {
                        remainingBits = remainingBits & ~bitMask
                        return Self(rawValue: bitMask)
                    }
                }
                return nil
            }
        }
    }
}

示例用法:

let weekdays: WeekdaySet = [.Monday, .Tuesday]
for weekday in weekdays.elements() {
    print(weekday)
}

// Output:
// WeekdaySet(rawValue: 2)
// WeekdaySet(rawValue: 4)

斯威夫特 3:

public extension OptionSet where RawValue : Integer {

    func elements() -> AnySequence<Self> {
        var remainingBits = rawValue
        var bitMask: RawValue = 1
        return AnySequence {
            return AnyIterator {
                while remainingBits != 0 {
                    defer { bitMask = bitMask &* 2 }
                    if remainingBits & bitMask != 0 {
                        remainingBits = remainingBits & ~bitMask
                        return Self(rawValue: bitMask)
                    }
                }
                return nil
            }
        }
    }
}

斯威夫特 4:

public extension OptionSet where RawValue: FixedWidthInteger {

    func elements() -> AnySequence<Self> {
        var remainingBits = rawValue
        var bitMask: RawValue = 1
        return AnySequence {
            return AnyIterator {
                while remainingBits != 0 {
                    defer { bitMask = bitMask &* 2 }
                    if remainingBits & bitMask != 0 {
                        remainingBits = remainingBits & ~bitMask
                        return Self(rawValue: bitMask)
                    }
                }
                return nil
            }
        }
    }
}

【讨论】:

  • 谢谢,马丁。您的解决方案看起来不错。但是,我觉得这应该更容易。我已经向 Apple 提交了一个错误。
  • 我很难称其为错误。 OptionSetType 不是数组,它仅符合 ArrayLiteralConvertable 协议,这意味着它可以使用数组进行实例化,而无需求助于专门的 init 方法。一旦你完成了它的创建,数组本身就不再相关了;它已被转换为整数。因此,我认为 Martin 的解决方案非常优雅,与使用无符号整数作为原始值的任何 OptionSetType 一样。
  • @Zmey:您必须添加另一个约束:set&lt;T: OptionSet where T.RawValue: UnsignedInteger, T.Element == T&gt;。 – 比较swiftdoc.org/v3.0/protocol/OptionSet“要从 OptionSet 协议继承所有默认实现,Element 类型必须是 Self,默认值。” – 我会检查是否可以摆脱该限制,但我认为这不是必需的。
  • NP :) 我开始阅读有关 defer 的更多信息,看起来 NSHipster 建议反对这种模式:nshipster.com/guard-and-defer(向下滚动到“(任何其他)延迟被认为是有害的”
  • @DavidJames:它使用RawValue: FixedWidthInteger 编译(并运行)。我会相应地更新答案,谢谢通知!
【解决方案2】:

根据之前的答案,我使用IteratorProtocol 创建了一个通用的 Swift 4 解决方案:

public struct OptionSetIterator<Element: OptionSet>: IteratorProtocol where Element.RawValue == Int {
    private let value: Element

    public init(element: Element) {
        self.value = element
    }

    private lazy var remainingBits = value.rawValue
    private var bitMask = 1

    public mutating func next() -> Element? {
        while remainingBits != 0 {
            defer { bitMask = bitMask &* 2 }
            if remainingBits & bitMask != 0 {
                remainingBits = remainingBits & ~bitMask
                return Element(rawValue: bitMask)
            }
        }
        return nil
    }
}

然后在OptionSet扩展中实现makeIterator()

假设您的OptionSets 将是Int

extension OptionSet where Self.RawValue == Int {
   public func makeIterator() -> OptionSetIterator<Self> {
      return OptionSetIterator(element: self)
   }
}

现在每次创建 OptionSet 时,只需将其符合 Sequence

struct WeekdaySet: OptionSet, Sequence {
    let rawValue: Int

    ...
}

您现在应该能够对其进行迭代:

let weekdays: WeekdaySet = [.monday, .tuesday]
for weekday in weekdays {
    // Do something with weekday
}

我还会创建一个类型别名来明确使用什么:

typealias SequenceOptionSet = OptionSet &amp; Sequence

【讨论】:

  • 工作得很好,比其他解决方案更“迅速”。
  • 你应该把Int改成FixedWidthInteger
【解决方案3】:

给你。我还添加了一个方便的初始化程序来减少一些样板:

enum Day: Int {
  case Sun, Mon, Tue, Wed, Thu, Fri, Sat
}

struct WeekdaySet: OptionSetType {

  let rawValue: UInt8

  init(rawValue: UInt8) {
    self.rawValue = rawValue
  }

  init(_ rawValue: UInt8) {
    self.init(rawValue: rawValue)
  }

  static let Sunday = WeekdaySet(1 << 0)
  static let Monday = WeekdaySet(1 << 1)
  static let Tuesday = WeekdaySet(1 << 2)
  static let Wednesday = WeekdaySet(1 << 3)
  static let Thursday = WeekdaySet(1 << 4)
  static let Friday = WeekdaySet(1 << 5)
  static let Saturday = WeekdaySet(1 << 6)
  static let AllDays = [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]

  subscript(indexes: Day...) -> [WeekdaySet] {
    var weekdaySets = [WeekdaySet]()

    for i in indexes {
      weekdaySets.append(WeekdaySet.AllDays[i.rawValue])
    }

    return weekdaySets
  }

}

for weekday in WeekdaySet()[Day.Mon, Day.Tue] {
  print(weekday)
}

【讨论】:

  • 谢谢,斯科特。但是,我想枚举 WeekdaySet 的特定实例的值,而不仅仅是所有工作日。
【解决方案4】:

使用此迷你库 (https://github.com/allexks/Options) 中的方法,您可以将您的工作日设置为常规的 enum 类型:

enum Weekday: CaseIterable {
    case sunday
    case monday
    case tuesday
    case wednesday
    case thursday
    case friday
    case saturday
}

这样您就可以轻松创建OptionSet

let weekdays: Options<Weekday> = [.monday, .tuesday]

然后为了进行迭代,您可以使用库提供的以下便利属性:

for weekday in weekdays.decomposed {
    // Do something with weekday
}

【讨论】:

    【解决方案5】:
    /// Day rawValues used in WeekdaySet. Day proper names capitalized.
    enum Day: UInt8, CaseIterable {
        case Sunday    = 0b00000001
        case Monday    = 0b00000010
        case Tuesday   = 0b00000100
        case Wednesday = 0b00001000
        case Thursday  = 0b00010000
        case Friday    = 0b00100000
        case Saturday  = 0b01000000
        var description: String {
            return "\(self)"
        }
    }
    
    /// Seven days of the week represented with binary options.
    struct WeekdaySet: OptionSet {
        
        /// WeekdaySet initialized with Day (not with Weekday)
        static let Sunday    = WeekdaySet(.Sunday)
        static let Monday    = WeekdaySet(.Monday)
        static let Tuesday   = WeekdaySet(.Tuesday)
        static let Wednesday = WeekdaySet(.Wednesday)
        static let Thursday  = WeekdaySet(.Thursday)
        static let Friday    = WeekdaySet(.Friday)
        static let Saturday  = WeekdaySet(.Saturday)
        
        /// WeekdaySet initialized with Weekday (not with Day)
        static let all: WeekdaySet = [.Sunday, .Monday, .Tuesday, .Wednesday, .Thursday, .Friday, .Saturday]
        static let weekend: WeekdaySet = [.Saturday, .Sunday]
        static let midweek: WeekdaySet = [.Tuesday, .Wednesday, .Thursday]
        static let humpday: WeekdaySet = .Wednesday
        
        /// OptionSet conformance
        let rawValue: UInt8
        init(rawValue: UInt8) {
            self.rawValue = rawValue
        }
        
        /// Init using the enum Day
        init (_ day: Day) {
            self.rawValue = day.rawValue
        }
        
    }
    
    extension WeekdaySet: CaseIterable {
    
        static var allCases: [WeekdaySet] {
            [
                .Sunday,
                .Monday,
                .Tuesday,
                .Wednesday,
                .Thursday,
                .Friday,
                .Saturday,
            ]
        }
        
        /// Computed instance property to filter static allCases
        var array: [WeekdaySet] {
            get {
                return WeekdaySet.allCases.filter { self.contains($0) }
            }
        }
    }
    
    extension WeekdaySet: Sequence {
     
        typealias Iterator = AnyIterator<WeekdaySet>
        
        func makeIterator() -> Iterator {
            var iterator = array.makeIterator()
            return AnyIterator {
                return iterator.next()
            }
        }
    }
    
    extension WeekdaySet: CustomStringConvertible {
        var description: String {
            if self.array.count < 2 {
                return Day(rawValue: self.rawValue)?.description ?? ""
                
            }
            var results: [String] = []
            for weekday in self.array {
                results.append(Day(rawValue: weekday.rawValue)?.description ?? "")
            }
            return String(describing: results)
        }
    }
    
    /// A example set of weekdays
    let customSet: WeekdaySet = [.Monday, .Tuesday]
    
    /// Example usages:
    print("Does the example set contain humpday?", customSet.contains(.humpday))
    
    for weekday in customSet {
        print("Is \(weekday) midweek?", WeekdaySet.midweek.contains(weekday))
    }
    
    print("Thursday:", WeekdaySet.Thursday)
    print("Weekend names:", WeekdaySet.weekend)
    print("All names", WeekdaySet.all)
    
    // Printed results:
    // Does the example set contain humpday? false
    // Is Monday midweek? false
    // Is Tuesday midweek? true
    // Thursday: Thursday
    // Weekend names: ["Sunday", "Saturday"]
    // All names ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
    

    【讨论】:

      猜你喜欢
      • 2016-08-17
      • 1970-01-01
      • 2020-10-19
      • 1970-01-01
      • 1970-01-01
      • 2021-05-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多