如何在Swift中创建NS_OPTIONS风格的位掩码枚举?

140
在苹果有关与 C API 交互的文档中,他们描述了如何将使用 NS_ENUM 标记的 C 风格枚举导入为 Swift 枚举。这很合理,由于 Swift 中已经提供了 enum 值类型,因此很容易看出如何创建自己的枚举。
在进一步阅读时,关于使用 NS_OPTIONS 标记的 C 风格选项,它说:
Swift 还会导入使用 NS_OPTIONS 宏标记的选项。虽然选项的行为类似于导入的枚举,但选项也可以支持一些按位操作,例如“&”,“|”和“~”。 在 Objective-C 中,您可以使用常量零(0)表示空选项集。在 Swift 中,使用 nil 表示没有任何选项。
考虑到 Swift 中没有 options 值类型,我们该如何创建一个 C 风格的 options 变量来使用?

4
马特的非常出名的“NSHipster”网站对 RawOptionsSetType 进行了广泛描述:http://nshipster.com/rawoptionsettype/ - Klaas
15个回答

268

Swift 3.0

与Swift 2.0几乎相同。OptionSetType被重命名为OptionSet,按照惯例枚举写成小写字母。

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)
}

Swift 3建议使用空数组字面量,而不是提供none选项:

let noOptions: 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 2.0

在Swift 2.0中,协议扩展处理了大部分样板代码,它们现在被导入为符合OptionSetType的结构体。(自Swift 2 beta 2起,RawOptionSetType已经消失。)声明要简单得多:

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)
}

现在我们可以使用基于集合的语义来处理 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

观察被 Swift 导入的 Objective-C 选项(例如 UIViewAutoresizing),我们可以看到这些选项被声明为符合协议 RawOptionSetTypestruct,而该协议又符合 _RawOptionSetTypeEquatableRawRepresentableBitwiseOperationsTypeNilLiteralConvertible。我们也可以像这样创建自己的选项:

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) }
}

现在我们可以像苹果文档中描述的那样处理这个新的选项集MyOptions,您可以使用类似于enum的语法。
let opt1 = MyOptions.FirstOption
let opt2: MyOptions = .SecondOption
let opt3 = MyOptions(4)

它也像我们期望的选项一样运行:

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")
}

我已经建立了一个生成器来创建Swift选项集合,无需进行查找和替换。 最新消息:针对Swift 1.1 beta 3 进行了修改。

1
除非我将“value”设置为“UInt32”,否则它对我没有用。您也不需要定义任何函数,相关函数已经为“RawOptionSet”定义(例如,“func | <T:RawOptionSet>(a:T,b:T) - > T”)。 - David Lawson
还有一种更易读的初始化位掩码值的方法。它在Swift标准库参考文档这里中提供,可能已经更新自此回答发布以来。 - Matthew Quiros
2
有没有使用枚举而不是结构体的解决方案?我需要我的代码与Objective-C兼容... - jowie
1
枚举碰撞类型:UInt32 { case Player = 1 case Wall = 2 case Star = 4 case Vortex = 8 case Finish = 16 } - mccoyLBI
1
在这种情况下,苹果的文档非常好。 - Mr Rogers
显示剩余17条评论

12

Xcode 6.1 Beta 2对RawOptionSetType协议进行了一些更改(请参见Airspeedvelocity博客文章Apple发行说明)。根据Nate Cook的示例,以下是更新后的解决方案。您可以像这样定义自己的选项集:

struct MyOptions : RawOptionSetType, BooleanType {
    private var value: UInt
    init(_ rawValue: UInt) { self.value = rawValue }

    // MARK: _RawOptionSetType
    init(rawValue: UInt) { self.value = rawValue }

    // MARK: NilLiteralConvertible
    init(nilLiteral: ()) { self.value = 0}

    // MARK: RawRepresentable
    var rawValue: UInt { return self.value }

    // MARK: BooleanType
    var boolValue: Bool { return self.value != 0 }

    // MARK: BitwiseOperationsType
    static var allZeros: MyOptions { return self(0) }

    // MARK: User defined bit values
    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) }
    static var All: MyOptions           { return self(0b111) }
}

然后可以像这样使用它来定义变量:

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

像这样测试比特:

let singleOption = MyOptions.FirstOption
let multipleOptions: MyOptions = singleOption | .SecondOption
if multipleOptions & .SecondOption {
    println("multipleOptions has SecondOption")
}

let allOptions = MyOptions.All
if allOptions & .ThirdOption {
    println("allOptions has ThirdOption")
}

8

来自文档的Swift 2.0示例:

struct PackagingOptions : OptionSetType {
    let rawValue: Int
    init(rawValue: Int) { self.rawValue = rawValue }

    static let Box = PackagingOptions(rawValue: 1)
    static let Carton = PackagingOptions(rawValue: 2)
    static let Bag = PackagingOptions(rawValue: 4)
    static let Satchel = PackagingOptions(rawValue: 8)
    static let BoxOrBag: PackagingOptions = [Box, Bag]
    static let BoxOrCartonOrBag: PackagingOptions = [Box, Carton, Bag]
}

您可以在这里找到它。


6
在Swift 2中(目前作为Xcode 7 beta的一部分),NS_OPTIONS风格的类型被导入为新的OptionSetType类型的子类型。由于新的Protocol Extensions特性和标准库中实现OptionSetType的方式,您可以声明自己的类型来扩展OptionsSetType并获得与导入的NS_OPTIONS风格类型相同的所有函数和方法。
但是这些函数不再基于位运算符。在C语言中使用一组非排他性布尔选项需要在字段中进行掩码和调整位,这只是一种实现细节。实际上,一组选项是一个集合...一个独特项的集合。因此,OptionsSetType从 SetAlgebraType 协议中获取所有方法,例如从数组文字语法创建、包含查询、与intersection掩码等(不再需要记住哪个有趣的字符用于哪个成员资格测试!)

5
//Swift 2.0
 //create
    struct Direction : OptionSetType {
        let rawValue: Int
        static let None   = Direction(rawValue: 0)
        static let Top    = Direction(rawValue: 1 << 0)
        static let Bottom = Direction(rawValue: 1 << 1)
        static let Left   = Direction(rawValue: 1 << 2)
        static let Right  = Direction(rawValue: 1 << 3)
    }
//declare
var direction: Direction = Direction.None
//using
direction.insert(Direction.Right)
//check
if direction.contains(.Right) {
    //`enter code here`
}

4

如果您不需要与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!")
}

等等。这里并没有实际地翻转位。这些是针对不透明值的设置操作。您可以在此处找到要点


@ChrisPrince 很可能是因为它是为 Swift 1.0 创建的,自那以后就没有更新过。 - Gregory Higley
我正在开发这个的 Swift 2.0 版本。 - Gregory Higley

2

为了帮助其他人,我在这里提供一个额外的例子,说明是否可以组合复合选项。答案是肯定的,如果你习惯了好老的位字段,它们的组合方式就像你预期的那样:

struct State: OptionSetType {
    let rawValue: Int
    static let A      = State(rawValue: 1 << 0)
    static let B      = State(rawValue: 1 << 1)
    static let X      = State(rawValue: 1 << 2)

    static let AB:State  = [.A, .B]
    static let ABX:State = [.AB, .X]    // Combine compound state with .X
}

let state: State = .ABX
state.contains(.A)        // true
state.contains(.AB)       // true

它将集合[.AB, .X]压平为[.A, .B, .X](至少在语义上):

print(state)      // 0b111 as expected: "State(rawValue: 7)"
print(State.AB)   // 0b11 as expected: "State(rawValue: 3)"

2
如果我们只需要一种方法来使用|组合选项,并使用&检查组合选项是否包含特定选项,那么Nate Cook的答案的替代方法可能是这样的:
创建一个选项协议protocol并重载|&
protocol OptionsProtocol {

    var value: UInt { get }
    init (_ value: UInt)

}

func | <T: OptionsProtocol>(left: T, right: T) -> T {
    return T(left.value | right.value)
}

func & <T: OptionsProtocol>(left: T, right: T) -> Bool {
    if right.value == 0 {
        return left.value == 0
    }
    else {
        return left.value & right.value == right.value
    }
}

现在我们可以更简单地创建选项结构体,如下所示:
struct MyOptions: OptionsProtocol {

    private(set) var value: UInt
    init (_ val: UInt) {value = val}

    static var None: MyOptions { return self(0) }
    static var One: MyOptions { return self(1 << 0) }
    static var Two: MyOptions { return self(1 << 1) }
    static var Three: MyOptions { return self(1 << 2) }
}

它们可以用于以下方式:

func myMethod(#options: MyOptions) {
    if options & .One {
        // Do something
    }
}

myMethod(options: .One | .Three) 

2

正如Rickster已经提到的,您可以在Swift 2.0中使用OptionSetType。NS_OPTIONS类型被导入为符合OptionSetType协议,该协议提供了一种选项集的接口:

struct CoffeeManipulators : OptionSetType {
    let rawValue: Int
    static let Milk     = CoffeeManipulators(rawValue: 1)
    static let Sugar    = CoffeeManipulators(rawValue: 2)
    static let MilkAndSugar = [Milk, Sugar]
}

它可以提供这种工作方式:
struct Coffee {
    let manipulators:[CoffeeManipulators]

    // You can now simply check if an option is used with contains
    func hasMilk() -> Bool {
        return manipulators.contains(.Milk)
    }

    func hasManipulators() -> Bool {
        return manipulators.count != 0
    }
}

1

关于使用带有多个选项的选项集创建沙盒和书签

let options:NSURL.BookmarkCreationOptions = [.withSecurityScope,.securityScopeAllowOnlyReadAccess]
let temp = try link.bookmarkData(options: options, includingResourceValuesForKeys: nil, relativeTo: nil)

需要合并创作选项的解决方案,在不是所有选项互斥时非常有用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接