可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
How should bit fields be declared and used in Swift?
Declaring an enum like this does work, but trying to OR 2 values together fails to compile:
enum MyEnum: Int { case One = 0x01 case Two = 0x02 case Four = 0x04 case Eight = 0x08 } // This works as expected let m1: MyEnum = .One // Compiler error: "Could not find an overload for '|' that accepts the supplied arguments" let combined: MyEnum = MyEnum.One | MyEnum.Four
I looked at how Swift imports Foundation enum types, and it does so by defining a struct
that conforms to the RawOptionSet
protocol:
struct NSCalendarUnit : RawOptionSet { init(_ value: UInt) var value: UInt static var CalendarUnitEra: NSCalendarUnit { get } static var CalendarUnitYear: NSCalendarUnit { get } // ... }
And the RawOptionSet
protocol is:
protocol RawOptionSet : LogicValue, Equatable { class func fromMask(raw: Self.RawType) -> Self }
However, there is no documentation on this protocol and I can't figure out how to implement it myself. Moreover, it's not clear if this is the official Swift way of implementing bit fields or if this is only how the Objective-C bridge represents them.
回答1:
You can build a struct
that conforms to the RawOptionSet
protocol, and you'll be able to use it like the built-in enum
type but with bitmask functionality as well. The answer here shows how: Swift NS_OPTIONS-style bitmask enumerations.
回答2:
They showed how to do this in one of the WWDC videos.
let combined = MyEnum.One.toRaw() | MyEnum.Four.toRaw()
Note that combined
will be Int
type and will actually get a compiler error if you specify let combined: MyEnum
. That is because there is no enum value for 0x05
which is the result of the expression.
回答3:
I think maybe some of the answers here are outdated with overcomplicated solutions? This works fine for me..
enum MyEnum: Int { case One = 0 case Two = 1 case Three = 2 case Four = 4 case Five = 8 case Six = 16 } let enumCombined = MyEnum.Five.rawValue | MyEnum.Six.rawValue if enumCombined & MyEnum.Six.rawValue != 0 { println("yay") // prints } if enumCombined & MyEnum.Five.rawValue != 0 { println("yay again") // prints } if enumCombined & MyEnum.Two.rawValue != 0 { println("shouldn't print") // doesn't print }
回答4:
Updated for Swift 2/3
Since swift 2, a new solution has been added as "raw option set" (see: Documentation), which is essentially the same as my original response, but using structs that allow arbitrary values.
This is the original question rewritten as an OptionSet
:
struct MyOptions: OptionSet { let rawValue: UInt8 static let One = MyOptions(rawValue: 0x01) static let Two = MyOptions(rawValue: 0x02) static let Four = MyOptions(rawValue: 0x04) static let Eight = MyOptions(rawValue: 0x08) } let m1 : MyOptions = .One let combined : MyOptions = [MyOptions.One, MyOptions.Four]
Combining with new values can be done exactly as Set
operations (thus the OptionSet part), .union
, likewise:
m1.union(.Four).rawValue // Produces 5
Same as doing One | Four
in its C-equivalent. As for One & Mask != 0
, can be specified as a non-empty intersection
// Equivalent of A & B != 0 if !m1.intersection(combined).isEmpty { // m1 belongs is in combined }
Weirdly enough, most of the C-style bitwise enums have been converted to their OptionSet
equivalent on Swift 3, but Calendar.Compontents
does away with a Set<Enum>
:
let compontentKeys : Set<Calendar.Component> = [.day, .month, .year]
Whereas the original NSCalendarUnit
was a bitwise enum. So both approaches are usable (thus the original response remains valid)
Original Response
I think the best thing to do, is to simply avoid the bitmask syntax until the Swift devs figure out a better way.
Most of the times, the problem can be solved using an enum
and and a Set
enum Options { case A, B, C, D } var options = Set<Options>(arrayLiteral: .A, .D)
An and check (options & .A
) could be defined as:
options.contains(.A)
Or for multiple "flags" could be:
options.isSupersetOf(Set<Options>(arrayLiteral: .A, .D))
Adding new flags (options |= .C
):
options.insert(.C)
This also allows for using all the new stuff with enums: custom types, pattern matching with switch case, etc.
Of course, it doesn't have the efficiency of bitwise operations, nor it would be compatible with low level things (like sending bluetooth commands), but it's useful for UI elements that the overhead of the UI outweighs the cost of the Set operations.
回答5:
If you don't need to interoperate with Objective-C and just want the syntax of bit masks in Swift, I've written a simple "library" called BitwiseOptions that can do this with regular Swift enumerations, e.g.:
enum Animal: BitwiseOptionsType { case Chicken case Cow case Goat static let allOptions = [.Chicken, .Cow, .Goat] } var animals = Animal.Chicken | Animal.Goat animals ^= .Goat if animals & .Chicken == .Chicken { println("Chick-Fil-A!") }
and so on. No actual bits are being flipped here. These are set operations on opaque values. You can find the gist here.
回答6:
@Mattt's very famous "NSHipster" has an extensive detailed description of the RawOptionsSetType
: http://nshipster.com/rawoptionsettype/
It includes a handy Xcode snipped:
struct <# Options #> : RawOptionSetType, BooleanType { private var value: UInt = 0 init(_ value: UInt) { self.value = value } var boolValue: Bool { return value != 0 } static func fromMask(raw: UInt) -> <# Options #> { return self(raw) } static func fromRaw(raw: UInt) -> <# Options #>? { return self(raw) } func toRaw() -> UInt { return value } static var allZeros: <# Options #> { return self(0) } static func convertFromNilLiteral() -> <# Options #> { return self(0) } static var None: <# Options #> { return self(0b0000) } static var <# Option #>: <# Options #> { return self(0b0001) } // ... }
回答7:
You have to use .toRaw() after each member:
let combined: Int = MyEnum.One.toRaw() | MyEnum.Four.toRaw()
will work. Because as it is you're just trying to assign "One" which is a MyEnum type, not an integer. As Apple's documentation says:
“Unlike C and Objective-C, Swift enumeration members are not assigned a default integer value when they are created. In the CompassPoints example, North, South, East and West do not implicitly equal 0, 1, 2 and 3. Instead, the different enumeration members are fully-fledged values in their own right, with an explicitly-defined type of CompassPoint.”
so you have to use raw values if you want the members to represent some other type, as described here:
Enumeration members can come prepopulated with default values (called raw values), which are all of the same type. The raw value for a particular enumeration member is always the same. Raw values can be strings, characters, or any of the integer or floating-point number types. Each raw value must be unique within its enumeration declaration. When integers are used for raw values, they auto-increment if no value is specified for some of the enumeration members. Access the raw value of an enumeration member with its toRaw method.
回答8:
I'm taking a guess that something like this is how they are modeling enum options in Foundation:
struct TestOptions: RawOptionSet { // conform to RawOptionSet static func fromMask(raw: UInt) -> TestOptions { return TestOptions(raw) } // conform to LogicValue func getLogicValue() -> Bool { if contains([1, 2, 4], value) { return true } return false } // conform to RawRepresentable static func fromRaw(raw: UInt) -> TestOptions? { if contains([1, 2, 4], raw) { return TestOptions(raw) } return nil } func toRaw() -> UInt { return value } // options and value var value: UInt init(_ value: UInt) { self.value = value } static var OptionOne: TestOptions { return TestOptions(1) } static var OptionTwo: TestOptions { return TestOptions(2) } static var OptionThree: TestOptions { return TestOptions(4) } } let myOptions = TestOptions.OptionOne | TestOptions.OptionThree println("myOptions: \(myOptions.toRaw())") if (myOptions & TestOptions.OptionOne) { println("OPTION ONE is in there") } else { println("nope, no ONE") } if (myOptions & TestOptions.OptionTwo) { println("OPTION TWO is in there") } else { println("nope, no TWO") } if (myOptions & TestOptions.OptionThree) { println("OPTION THREE is in there") } else { println("nope, no THREE") } let nextOptions = myOptions | TestOptions.OptionTwo println("options: \(nextOptions.toRaw())") if (nextOptions & TestOptions.OptionOne) { println("OPTION ONE is in there") } else { println("nope, no ONE") } if (nextOptions & TestOptions.OptionTwo) { println("OPTION TWO is in there") } else { println("nope, no TWO") } if (nextOptions & TestOptions.OptionThree) { println("OPTION THREE is in there") } else { println("nope, no THREE") }
...where myOptions
and nextOptions
are of type TestOptions - I'm not exactly sure how fromMask()
and getLogicValue()
are supposed to act here (I just took some best guesses), maybe somebody could pick this up and work it out?
回答9:
If you want bitfield in Swift, then enum is the wrong way. Better just do like this
class MyBits { static let One = 0x01 static let Two = 0x02 static let Four = 0x04 static let Eight = 0x08 } let m1 = MyBits.One let combined = MyBits.One | MyBits.Four
You don't really need the class/static wrapper, but I include it as a kind of pseudo namespace.
回答10:
Do bitwise operation using raw value then create a new enum object using the result.
let mask = UIViewAutoresizing(rawValue: UIViewAutoresizing.FlexibleWidth.rawValue|UIViewAutoresizing.FlexibleHeight.rawValue) self.view.autoresizingMask = mask
回答11:
Here's something I put together to try to make a Swift enum that resembles to some extent a C# flags-style enum. But I'm just learning Swift, so this should only be considered to be "proof of concept" code.
/// This EnumBitFlags protocol can be applied to a Swift enum definition, providing a small amount /// of compatibility with the flags-style enums available in C#. /// /// The enum should be defined as based on UInt, and enum values should be defined that are powers /// of two (1, 2, 4, 8, ...). The value zero, if defined, should only be used to indicate a lack of /// data or an error situation. /// /// Note that with C# the enum may contain a value that does not correspond to the defined enum /// constants. This is not possible with Swift, it enforces that only valid values can be set. public protocol EnumBitFlags : RawRepresentable, BitwiseOperations { var rawValue : UInt { get } // This provided automatically by enum static func createNew(_ rawValue : UInt) -> Self // Must be defined as some boiler-plate code } /// Extension methods for enums that implement the EnumBitFlags protocol. public extension EnumBitFlags { // Implement protocol BitwiseOperations. But note that some of these operators, especially ~, // will almost certainly result in an invalid (nil) enum object, resulting in a crash. public static func & (leftSide: Self, rightSide: Self) -> Self { return self.createNew(leftSide.rawValue & rightSide.rawValue) } public static func | (leftSide: Self, rightSide: Self) -> Self { return self.createNew(leftSide.rawValue | rightSide.rawValue) } public static func ^ (leftSide: Self, rightSide: Self) -> Self { return self.createNew(leftSide.rawValue ^ rightSide.rawValue) } public static prefix func ~ (x: Self) -> Self { return self.createNew(~x.rawValue) } public static var allZeros: Self { get { return self.createNew(0) } } // Method hasFlag() for compatibility with C# func hasFlag<T : EnumBitFlags>(_ flagToTest : T) -> Bool { return (self.rawValue & flagToTest.rawValue) != 0 } }
This shows how it can be used:
class TestEnumBitFlags { // Flags-style enum specifying where to write the log messages public enum LogDestination : UInt, EnumBitFlags { case none = 0 // Error condition case systemOutput = 0b01 // Logging messages written to system output file case sdCard = 0b10 // Logging messages written to SD card (or similar storage) case both = 0b11 // Both of the above options // Implement EnumBitFlags protocol public static func createNew(_ rawValue : UInt) -> LogDestination { return LogDestination(rawValue: rawValue)! } } private var _logDestination : LogDestination = .none private var _anotherEnum : LogDestination = .none func doTest() { _logDestination = .systemOutput assert(_logDestination.hasFlag(LogDestination.systemOutput)) assert(!_logDestination.hasFlag(LogDestination.sdCard)) _anotherEnum = _logDestination assert(_logDestination == _anotherEnum) _logDestination = .systemOutput | .sdCard assert(_logDestination.hasFlag(LogDestination.systemOutput) && _logDestination.hasFlag(LogDestination.sdCard)) /* don't do this, it results in a crash _logDestination = _logDestination & ~.systemOutput assert(_logDestination == .sdCard) */ _logDestination = .sdCard _logDestination |= .systemOutput assert(_logDestination == .both) } }
Suggestions for improvement are welcome.
EDIT: I've given up on this technique myself, and therefore obviously can't recommend it anymore.
The big problem is that Swift demands that rawValue must match one of the defined enum values. This is OK if there are only 2 or 3 or maybe even 4 flag bits - just define all of the combination values in order to make Swift happy. But for 5 or more flag bits it becomes totally crazy.
I'll leave this posted in case someone finds it useful, or maybe as a warning of how NOT to do it.
My current solution to this situation is based on using a struct instead of enum, together with a protocol and some extension methods. This works much better. Maybe I'll post it someday when I'm more sure that that isn't also isn't going to backfire on me.
回答12:
I use the following I need the both values I can get, rawValue for indexing arrays and value for flags.
enum MyEnum: Int { case one case two case four case eight var value: UInt8 { return UInt8(1 << self.rawValue) } } let flags: UInt8 = MyEnum.one.value ^ MyEnum.eight.value (flags & MyEnum.eight.value) > 0 // true (flags & MyEnum.four.value) > 0 // false (flags & MyEnum.two.value) > 0 // false (flags & MyEnum.one.value) > 0 // true MyEnum.eight.rawValue // 3 MyEnum.four.rawValue // 2