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:
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:
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
Access Class Property from Instance
Is There a Method to Check The UIcollectionview Item Drag Cancellation If The Item Wasn't Moved
How to Check If a Variable Is Nil
Swift: Binary Operator '==' Cannot Be Applied to Operands of Type "Protocol"
How Is Commoncrypto Used in Swift3
Difference When Declaring Swift Protocol Using Inheritance from Another Protocol or Using Where Self
Perform Segue After UIalertcontroller Is Dismissed
Binary to Hexadecimal in Swift
Using Associatedtype in a Delegate Protocol for a Generic Type
How to Change Font Size of Nstableheadercell
When How to Start Submitting Apps to The iOS App Store Written Using The Swift Programming Language
Table View's 'Cellforrow(At:)' Is 'Nil' in Unit Test
Nil Cannot Be Assigned to Type Avcapturedeviceinput
"The Requested Snapshot Version Is Too Old." Error in Firestore