Switch to Match Multiple Cases from Optionsettype

Switch to match multiple cases from OptionSetType

OptionSetType is a subtype of SetAlgebraType, so you can use set algebra methods to test one combination of options against another. Depending on exactly what you want to ask (and what the sets in question are), there may be multiple ways to do it.

First, I'll put the attributes I'm querying for in a local constant:

let youngBoy: Person = [.Young, .Boy] 

Now, to use that for one kind of test that works well:

if him.isSupersetOf(youngBoy) {
// go to Toshi station, pick up power converters
}

Of course, what this is specifically asking is whether him contains all the options listed in youngBoy. That might be all you care about, so that's just fine. And it's also safe if you later extend Person to have other options, too.

But what if Person had other possible options, and you wanted to assert that him contains exactly the options listed in youngBoy, and no others? SetAlgebraType extends Equatable, so you can just test with ==:

if him == youngBoy {
// he'd better have those units on the south ridge repaired by midday
}

By the way, you don't want to use a switch statement for this. Switch is for selecting one out of several possible cases (is it A or B?), so using it to test combinatorics (is it A, B, A and B, or neither?) makes your code unwieldy.

OptionSet with associated value for each option

But that does not compile. In fact, it looks like the filter property is not separately defined for each option, but rather for a whole option set.

This is because an OptionSet isn't really a set, per se. If I have the following OptionSet:

struct MyOptions: OptionSet {
let rawValue: Int

static let foo = MyOptions(1 << 0)
static let bar = MyOptions(1 << 1)
}

and then I make the set like so:

let opts: MyOptions = [.foo, .bar]

I don't actually have a collection with two MyOptions instances in it. Instead, I have a new instance of MyOptions whose rawValue is set to (.foo.rawValue | .bar.rawValue)—i.e. 3. The original two MyOptions instances are discarded as soon as opts is made.

Similarly, your logger.outputs will be an instance of OutputOptions with rawValue 3 and the default value for filter.

Thus, it's not really possible to do what you want with an OptionSet.

How to use NS_OPTIONS defined in Obj-C class in Swift

First, you need to fix your syntax. You need a semicolon after the closing brace:

typedef NS_OPTIONS(NSUInteger, MyOption) {
MyOptionNone = 0,
MyOptionTop = 1 << 0,
MyOptionLeft = 1 << 1,
MyOptionBottom = 1 << 2,
MyOptionRight = 1 << 3
};

Next, in your bridging header, you need to import the header file that defines MyOption. When you first create a Swift source file to an Objective-C project, or when you first create an Objective-C source file in a Swift project, Xcode offers to create the bridging header for you. It's named ProjectName-Bridging-Header.h. So for example:

bridging header

Once you've done this, and both header files can be compiled without errors, you can use MyOption from Swift. It's an OptionSetType. Example:

MyOption usage

How to create NS_OPTIONS-style bitmask enumerations in Swift?

Swift 3.0

Almost identical to Swift 2.0. OptionSetType was renamed to OptionSet and enums are written lower case by convention.

struct MyOptions : OptionSet {
let rawValue: Int

static let firstOption = MyOptions(rawValue: 1 << 0)
static let secondOption = MyOptions(rawValue: 1 << 1)
static let thirdOption = MyOptions(rawValue: 1 << 2)
}

Instead of providing a none option, the Swift 3 recommendation is to simply use an empty array literal:

let noOptions: MyOptions = []

Other usage:

let singleOption = MyOptions.firstOption
let multipleOptions: MyOptions = [.firstOption, .secondOption]
if multipleOptions.contains(.secondOption) {
print("multipleOptions has SecondOption")
}
let allOptions = MyOptions(rawValue: 7)
if allOptions.contains(.thirdOption) {
print("allOptions has ThirdOption")
}

Swift 2.0

In Swift 2.0, protocol extensions take care of most of the boilerplate for these, which are now imported as a struct that conforms to OptionSetType. (RawOptionSetType has disappeared as of Swift 2 beta 2.) The declaration is far simpler:

struct MyOptions : OptionSetType {
let rawValue: Int

static let None = MyOptions(rawValue: 0)
static let FirstOption = MyOptions(rawValue: 1 << 0)
static let SecondOption = MyOptions(rawValue: 1 << 1)
static let ThirdOption = MyOptions(rawValue: 1 << 2)
}

Now we can use set-based semantics with MyOptions:

let singleOption = MyOptions.FirstOption
let multipleOptions: MyOptions = [.FirstOption, .SecondOption]
if multipleOptions.contains(.SecondOption) {
print("multipleOptions has SecondOption")
}
let allOptions = MyOptions(rawValue: 7)
if allOptions.contains(.ThirdOption) {
print("allOptions has ThirdOption")
}

Swift 1.2

Looking at the Objective-C options that were imported by Swift (UIViewAutoresizing, for example), we can see that options are declared as a struct that conforms to protocol RawOptionSetType, which in turn conforms to _RawOptionSetType, Equatable, RawRepresentable, BitwiseOperationsType, and NilLiteralConvertible. We can create our own like this:

struct MyOptions : RawOptionSetType {
typealias RawValue = UInt
private var value: UInt = 0
init(_ value: UInt) { self.value = value }
init(rawValue value: UInt) { self.value = value }
init(nilLiteral: ()) { self.value = 0 }
static var allZeros: MyOptions { return self(0) }
static func fromMask(raw: UInt) -> MyOptions { return self(raw) }
var rawValue: UInt { return self.value }

static var None: MyOptions { return self(0) }
static var FirstOption: MyOptions { return self(1 << 0) }
static var SecondOption: MyOptions { return self(1 << 1) }
static var ThirdOption: MyOptions { return self(1 << 2) }
}

Now we can treat this new option set, MyOptions, just like described in Apple's documentation: you can use enum-like syntax:

let opt1 = MyOptions.FirstOption
let opt2: MyOptions = .SecondOption
let opt3 = MyOptions(4)

And it also behaves like we'd expect options to behave:

let singleOption = MyOptions.FirstOption
let multipleOptions: MyOptions = singleOption | .SecondOption
if multipleOptions & .SecondOption != nil { // see note
println("multipleOptions has SecondOption")
}
let allOptions = MyOptions.fromMask(7) // aka .fromMask(0b111)
if allOptions & .ThirdOption != nil {
println("allOptions has ThirdOption")
}

I've built a generator to create a Swift option set without all the find/replacing.

Latest: Modifications for Swift 1.1 beta 3.

Best way to test mutual membership for set of enums

You could avoid the duplication using a static dictionary in one of the enums:

extension BeverageType  
{
var associatedBeverages:[Beverage] { return Beverage.associatedBeverages[self]! }
}

extension Beverage
{
var beverageType:BeverageType { return Beverage.beverageTypes[self]! }

static var beverageTypes:[Beverage:BeverageType]
= [
.cola : .coldBeverage,
.milk : .coldBeverage,
.wine : .coldBeverage,
.coffee : .hotBeverage,
.tea : .hotBeverage,
.hotChocolate : .hotBeverage
]

static var associatedBeverages:[BeverageType:[Beverage]] =
{
var beveragesByType:[BeverageType:[Beverage]] = [:]
Beverage.beverageTypes.forEach
{beveragesByType[$0.1] = (beveragesByType[$0.1] ?? []) + [$0.0]}
return beveragesByType
}()
}

This approach does not require duplicating the list of enum entries (in addition to the mapping, which you have to do somewhere). It is also more efficient than a sequential search which could become significant for large or frequently used enums.

The static variables are evaluated only once, so from the second use onward, you benefit from the O(1) performance of dictionaries in both direction of the relationship.

Note that you could build the dictionaries the other way around (i.e. from [BeverageType:[Beverage]] to [Beverage:BeverageType]) and you could also place the static variables in each enum or all in the BeverageType enum.

I felt that beverages should know their BeverageType and are more likely to be expanded to new drinks so I chose to define the relationship in that (many to one) direction.

This could even be generalized further by defining a bidirectional Dictionary (generic) class to use in these situations so that the boiler plate code for the inverted dictionary doesn't pollute the extension.

[EDIT] With a bidirectional dictionary for the relation, the definition becomes even cleaner:

extension BeverageType  
{
var associatedBeverages:[Beverage] { return Beverage.beverageTypes[self] }
}

extension Beverage
{
var beverageType:BeverageType { return Beverage.beverageTypes[self]! }

static var beverageTypes = ManyToOne<Beverage,BeverageType>(
[
.coldBeverage : [.cola, .milk, .wine],
.hotBeverage : [.coffee, .tea, .hotChocolate]
])
}

struct ManyToOne<M:Hashable,O:Hashable>
{
var manyToOne:[M:O] = [:]
var oneToMany:[O:[M]] = [:]

init( _ m2o:[M:O] )
{
manyToOne = m2o
for (many,one) in m2o { oneToMany[one] = (oneToMany[one] ?? []) + [many] }
}

init( _ o2m:[O:[M]])
{
oneToMany = o2m
for (one,many) in o2m { many.forEach{ manyToOne[$0] = one } }
}

subscript(many:M) -> O? { return manyToOne[many] }
subscript(one:O) -> [M] { return oneToMany[one] ?? [] }
}

How do I get the count of a Swift enum?

As of Swift 4.2 (Xcode 10) you can declare
conformance to the CaseIterable protocol, this works for all
enumerations without associated values:

enum Stuff: CaseIterable {
case first
case second
case third
case forth
}

The number of cases is now simply obtained with

print(Stuff.allCases.count) // 4

For more information, see

  • SE-0194 Derived Collection of Enum Cases


Related Topics



Leave a reply



Submit