如何在Swift中声明和使用位字段?
声明这样的枚举确实有效,但尝试将OR 2值一起编译失败:
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
我查看了Swift如何导入Foundation枚举类型,并通过定义符合struct
协议的RawOptionSet
来实现:
struct NSCalendarUnit : RawOptionSet {
init(_ value: UInt)
var value: UInt
static var CalendarUnitEra: NSCalendarUnit { get }
static var CalendarUnitYear: NSCalendarUnit { get }
// ...
}
RawOptionSet
协议是:
protocol RawOptionSet : LogicValue, Equatable {
class func fromMask(raw: Self.RawType) -> Self
}
但是,没有关于此协议的文档,我无法弄清楚如何自己实现它。此外,目前尚不清楚这是否是Swift正式的实现位字段的方式,或者这只是Objective-C网桥如何表示它们。
答案 0 :(得分:25)
您可以构建符合struct
协议的RawOptionSet
,并且您可以像内置enum
类型一样使用它,但具有位掩码功能好。这里的答案显示了如何:
Swift NS_OPTIONS-style bitmask enumerations
答案 1 :(得分:13)
他们在其中一个WWDC视频中展示了如何做到这一点。
let combined = MyEnum.One.toRaw() | MyEnum.Four.toRaw()
请注意,combined
将为Int
类型,如果指定let combined: MyEnum
,实际上会出现编译错误。这是因为0x05
没有枚举值,这是表达式的结果。
答案 2 :(得分:12)
自swift 2以来,添加了一个新的解决方案作为“原始选项集”(see: Documentation),它与原始响应基本相同,但使用允许任意值的结构。
这是原始问题重写为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]
结合新值可以完全按照Set
操作(因此选项设置部分).union
完成,同样地:
m1.union(.Four).rawValue // Produces 5
与在其C等效中执行One | Four
相同。至于One & Mask != 0
,可以指定为非空交集
// Equivalent of A & B != 0
if !m1.intersection(combined).isEmpty
{
// m1 belongs is in combined
}
奇怪的是,大多数C风格的按位枚举已转换为Swift 3上的OptionSet
等效内容,但Calendar.Compontents
取消了Set<Enum>
:
let compontentKeys : Set<Calendar.Component> = [.day, .month, .year]
而原始NSCalendarUnit
是一个按位枚举。因此两种方法都是可用的(因此原始响应仍然有效)
我认为最好的办法是简单地避免使用bitmask语法,直到Swift开发人员找到更好的方法。
大多数情况下,使用enum
和Set
enum Options
{
case A, B, C, D
}
var options = Set<Options>(arrayLiteral: .A, .D)
An和check(options & .A
)可以定义为:
options.contains(.A)
或者对于多个“标志”可能是:
options.isSupersetOf(Set<Options>(arrayLiteral: .A, .D))
添加新标记(options |= .C
):
options.insert(.C)
这也允许使用枚举的所有新东西:自定义类型,与开关案例的模式匹配等。
当然,它没有按位操作的效率,也不兼容低级别的东西(比如发送蓝牙命令),但它对UI元素有用,UI的开销超过了成本。设置操作。
答案 3 :(得分:11)
我认为这里的一些答案可能已经过时了,过于复杂的解决方案?这对我来说很好..
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 :(得分:7)
如果您不需要与Objective-C进行互操作,只需要在Swift中使用位掩码的语法,我就编写了一个名为BitwiseOptions的简单“库”,它可以通过常规来实现Swift枚举,例如:
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!")
}
等等。这里没有翻转实际位。这些是对不透明值的设置操作。你可以找到要点here。
答案 5 :(得分:3)
@Mattt非常着名的“NSHipster”对RawOptionsSetType
:http://nshipster.com/rawoptionsettype/
它包含一个方便的Xcode剪辑:
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) }
// ...
}
答案 6 :(得分:2)
你必须在每个成员之后使用.toRaw():
let combined: Int = MyEnum.One.toRaw() | MyEnum.Four.toRaw()
会奏效。因为它只是在尝试分配一个MyEnum类型的“One”,不是一个整数。正如Apple's documentation所说:
“与C和Objective-C不同,Swift枚举成员在创建时未分配默认整数值。在CompassPoints示例中,North,South,East和West不会隐式地等于0,1,2和3.相反,不同的枚举成员本身就是完全成熟的值,具有明确定义的CompassPoint类型。“
因此,如果您希望成员代表其他类型,您必须使用原始值,如here所述:
枚举成员可以预填充默认值(称为原始值),它们都是相同的类型。特定枚举成员的原始值始终相同。原始值可以是字符串,字符或任何整数或浮点数类型。每个原始值在其枚举声明中必须是唯一的。当整数用于原始值时,如果没有为某些枚举成员指定值,则它们会自动递增。使用toRaw方法访问枚举成员的原始值。
答案 7 :(得分:1)
我猜这样的事情就是他们在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")
}
...其中myOptions
和nextOptions
属于TestOptions类型 - 我不确定fromMask()
和getLogicValue()
应该如何在这里行动(我刚刚接受了)一些最好的猜测),也许有人可以选择这个并解决它?
答案 8 :(得分:1)
如果你想在Swift中使用bitfield,那么枚举是错误的。更好的就是这样做
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
您并不真正需要类/静态包装器,但我将其作为一种伪命名空间包含在内。
答案 9 :(得分:1)
我使用以下内容我需要我可以得到的两个值,rawValue用于索引数组和标记值。
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
答案 10 :(得分:0)
使用原始值进行按位操作,然后使用结果创建新的枚举对象。
let mask = UIViewAutoresizing(rawValue: UIViewAutoresizing.FlexibleWidth.rawValue|UIViewAutoresizing.FlexibleHeight.rawValue)
self.view.autoresizingMask = mask
答案 11 :(得分:0)
这是我放在一起试图制作一个类似于C#flags-style枚举的Swift枚举的东西。但我只是学习Swift,所以这只应该被认为是“概念验证”代码。
/// 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
}
}
这显示了如何使用它:
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)
}
}
欢迎提出改进建议。
编辑:我自己放弃了这项技术,因此显然不能再推荐它了。最大的问题是Swift要求rawValue必须匹配其中一个定义的枚举值。如果只有2个或3个甚至4个标志位,这是可以的 - 只需定义所有组合值,以使Swift满意。但是对于5个或更多的标志位,它变得非常疯狂。
如果有人发现它有用,或者作为对不怎么做的警告,我会留下这个。
我目前解决这种情况的方法是基于使用struct而不是enum,以及协议和一些扩展方法。这样做效果更好。也许有一天我会发布它,当我更确定那不是也不会适得其反。
答案 12 :(得分:0)
从flags
获取所有flags_combination
。每个flag
和flags_combination
是整数。 flags_combination = flag_1 | flags_2
import Foundation
protocol FlagPrototype: CaseIterable, RawRepresentable where RawValue == Int {}
extension FlagPrototype {
init?(rawValue: Int) {
for flag in Self.allCases where flag.rawValue == rawValue {
self = flag
return
}
return nil
}
static func all(from combination: Int) -> [Self] {
return Self.allCases.filter { return combination | $0.rawValue == combination }
}
}
enum Flag { case one, two, three }
extension Flag: FlagPrototype {
var rawValue: Int {
switch self {
case .one: return 0x1
case .two: return 0x2
case .three: return 0x4
}
}
}
var flags = Flag.two.rawValue | Flag.three.rawValue
let selectedFlags = Flag.all(from: flags)
print(selectedFlags)
if selectedFlags == [.two, .three] { print("two | three") }