SwiftでNS_OPTIONSスタイルのビットマスク列挙を作成する方法は?


137

C APIとの相互作用に関するAppleのドキュメントでは、NS_ENUMマークされたCスタイルの列挙がSwift列挙としてインポートされる方法を説明しています。これは理にかなっており、Swiftの列挙enum型は値の型として簡単に提供されるため、独自の型を作成する方法を簡単に確認できます。

さらに下に、NS_OPTIONSマークされたCスタイルのオプションについて次のように書かれています。

Swiftは、NS_OPTIONSマクロでマークされたオプションもインポートします。オプションは、インポートされた列挙型と同様に振る舞うのに対し、オプションもなど、いくつかのビット単位の操作をサポートすることができ&|~。Objective-Cでは、空のオプションセットを定数ゼロ(0)で表します。Swiftでは、nilオプションを使用しないことを表すために使用します。

optionsSwiftに値タイプがない場合、Cスタイルのオプション変数を作成して操作するにはどうすればよいですか?


3
@マットの非常に有名な「NSHipster」には、nshipster.comRawOptionsSetType / rawoptionsettype
Klaas

回答:


258

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

noneオプションを提供する代わりに、Swift 3の推奨は単に空の配列リテラルを使用することです:

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ます。これらは現在、に準拠する構造体としてインポートされています。(RawOptionSetTypeSwift 2ベータ2で廃止されました。)宣言ははるかに簡単です:

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

(スウィフトが輸入したObjective-Cのオプションを見てみるとUIViewAutoresizing、例えば)、我々はオプションは次のように宣言されていることがわかりますstructプロトコルに準拠していることをRawOptionSetType、順番に準拠する _RawOptionSetTypeEquatableRawRepresentableBitwiseOperationsType、とNilLiteralConvertible。次のようにして独自のものを作成できます。

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で、Appleのドキュメントで説明されているのと同じように、この新しいオプションセットを処理できます。- like 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ベータ3の変更。


1
私が作らない限り、それは私にとってはうまくいきませんでしvalueUInt32。また、関数を定義する必要はありません。関連する関数はすでにRawOptionSetsに定義されています(例func |<T : RawOptionSet>(a: T, b: T) -> T
David Lawson

おかげで、関数についての素晴らしい点-コンパイラーは、プロトコルの残りの部分を適切に整えていなかったときに、それらについて不平を言っていたと思います。どのような問題がありましたUIntか?それは私にとってはうまくいきます。
ネイト・クック

2
構造体の代わりに列挙型を使用するソリューションはありますか?Objective-Cとの互換性が必要です...
jowie

1
@jowieenum CollisionTypes: UInt32 { case Player = 1 case Wall = 2 case Star = 4 case Vortex = 8 case Finish = 16 }
mccoyLBI

1
この場合、Appleのドキュメントは非常に優れています。
ロジャース氏

12

Xcode 6.1 Beta 2では、RawOptionSetTypeプロトコルにいくつかの変更が加えられました(このAirspeedvelocityブログエントリAppleリリースノートを参照)。

ネイトクックスの例に基づく、ここに更新されたソリューションがあります。次のように独自のオプションセットを定義できます。

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ベータの一部としてベータ)では、NS_OPTIONS-styleタイプは新しいタイプのサブタイプとしてインポートされOptionSetTypeます。また、新しいプロトコル拡張機能とOptionSetType標準ライブラリに実装されている方法のおかげで、拡張された独自の型を宣言してOptionsSetType、インポートされたNS_OPTIONSスタイルの型と同じすべての関数とメソッドを取得できます。

しかし、これらの関数はもはやビットごとの算術演算子に基づいていません。Cで非排他的なブールオプションのセットを操作するには、フィールドのビットをマスキングおよびいじる必要があることは実装の詳細です。本当に、オプションのセットは、ユニークなアイテムのコレクションです。そのため、配列リテラル構文からの作成、のようなクエリ、でのマスキングなどOptionsSetTypeSetAlgebraTypeプロトコルからすべてのメソッドを取得します(どのメンバーシップテストでどの面白い文字を使用するかを覚える必要はありません!)containsintersection


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のビットマスクの表面セマンティクスが必要な場合は、通常のSwift列挙でこれを実行できるBitwiseOptionsと呼ばれる単純な「ライブラリ」を作成しました。

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、2015年

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

2

私たちが必要としている唯一の機能がオプションを組み合わせる方法で|あり、組み合わせたオプションに特定のオプションが含まれているかどうかをチェックする方法がある場合、&ネイトクックの答えは次のようになります。

オプション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

複合オプションを組み合わせることができるか疑問に思っている他の人のために、追加の例を投稿するだけです。できます。古き良きビットフィールドに慣れている場合は、期待どおりに組み合わせます。

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

1

他の誰もそれについて言及していません-そして私はいくつかのいじくりの後でそれにそれについてちょっと戸惑いました-しかしSwiftセットはかなりうまく機能しているようです。

ビットマスクが実際に何を表しているかを(多分ベン図に?)考えると、それはおそらく空のセットになります。

もちろん、第一原理から問題に取り組む際、ビット単位の演算子の利便性は失われますが、読みやすさを向上させる強力なセットベースのメソッドが得られます。

たとえば、ここでは私のいじくり回しです:

enum Toppings : String {
    // Just strings 'cause there's no other way to get the raw name that I know of...
    // Could be 1 << x too...
    case Tomato = "tomato"
    case Salami = "salami"
    case Cheese = "cheese"
    case Chicken = "chicken"
    case Beef = "beef"
    case Anchovies = "anchovies"

    static let AllOptions: Set<Toppings> = [.Tomato, .Salami, .Cheese, .Chicken, .Anchovies, .Beef]
}

func checkPizza(toppings: Set<Toppings>) {
    if toppings.contains(.Cheese) {
        print("Possible dairy allergies?")
    }

    let meats: Set<Toppings> = [.Beef, .Chicken, .Salami]
    if toppings.isDisjointWith(meats) {
        print("Vego-safe!")
    }
    if toppings.intersect(meats).count > 1 {
        print("Limit one meat, or 50¢ extra charge!")
    }

    if toppings == [Toppings.Cheese] {
        print("A bit boring?")
    }
}

checkPizza([.Tomato, .Cheese, .Chicken, .Beef])

checkPizza([.Cheese])

Cスタイルのソリューションを採用しようとするのではなく、Swiftと同様に、問題に対する第一原理アプローチから来ていると感じているので、これは素晴らしいと思います。

この異なるパラダイムに挑戦するいくつかのObj-Cの使用例についても聞きたいと思います。この場合、生の整数値はまだメリットを示しています。


1

使用する際に避けられないビット位置を符号化するハード回避するために(1 << 0)(1 << 1)(1 << 15)など、またはさらに悪いことに1216384など、またはいくつかの進数の変化を、一方は第一のビットを定義することができenum、列挙ビット序計算を行い、次いで前記ましょう。

// Bits
enum Options : UInt {
    case firstOption
    case secondOption
    case thirdOption
}

// Byte
struct MyOptions : OptionSet {
    let rawValue: UInt

    static let firstOption  = MyOptions(rawValue: 1 << Options.firstOption.rawValue)
    static let secondOption = MyOptions(rawValue: 1 << Options.secondOption.rawValue)
    static let thirdOption  = MyOptions(rawValue: 1 << Options.thirdOption.rawValue)
}

何もハードコーディングする必要がない例を追加しました。
Peter Ahlberg 2017

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

さらに必要な場合は、計算されたプロパティを追加するだけです。

enum MyEnum: Int {
    case one
    case two
    case four
    case eight

    var value: UInt8 {
        return UInt8(1 << self.rawValue)
    }

    var string: String {
        switch self {
        case .one:
            return "one"
        case .two:
            return "two"
        case .four:
            return "four"
        case .eight:
            return "eight"
        }
    }
}

1

re:複数のオプションを含むオプションセットを使用したサンドボックスとブックマークの作成

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

すべてのオプションが相互に排他的ではない場合に役立つ、作成のためにオプションを組み合わせる必要があるソリューション。


0

ネイトの答えは良いですが、私はそれをDIYにしたいと思います:

struct MyOptions : OptionSetType {
    let rawValue: Int

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

0

オプションセットタイプを使用する OptionSet

struct ShippingOptions: OptionSet {
    let rawValue: Int

    static let nextDay    = ShippingOptions(rawValue: 1 << 0)
    static let secondDay  = ShippingOptions(rawValue: 1 << 1)
    static let priority   = ShippingOptions(rawValue: 1 << 2)
    static let standard   = ShippingOptions(rawValue: 1 << 3)

    static let express: ShippingOptions = [.nextDay, .secondDay]
    static let all: ShippingOptions = [.express, .priority, .standard]
}

1
これは多かれ少なかれすでにこの回答でカバーされています
パンジュン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.