Swift에서 NS_OPTIONS 스타일 비트 마스크 열거를 만드는 방법은 무엇입니까?


137

C API와의 상호 작용에 대한 Apple의 설명서에서 C NS_ENUM스타일 열거를 Swift 열거로 가져 오는 방법을 설명합니다 . 이것은 의미가 있으며 Swift의 열거 형은 다음과 같이 쉽게 제공됩니다.enum 값 유형 자체 작성 방법을 쉽게 볼 수 있습니다.

아래에 NS_OPTIONS표시된 C 스타일 옵션 에 대해 다음과 같이 말합니다 .

Swift는 NS_OPTIONS매크로 로 표시된 옵션도 가져옵니다 . 옵션은 수입 열거에 유사하게 동작하는 반면, 옵션은 다음과 같은 몇 가지 비트 연산을 지원할 수 &, |~. Objective-C에서 상수 0 ( 0)으로 설정된 빈 옵션을 나타냅니다 . Swift에서는 nil옵션이 없음을 나타내는 데 사용 하십시오.

optionsSwift에 값 유형 이없는 경우 C 스타일 옵션 변수를 작성하여 어떻게 작업 할 수 있습니까?


3
@Mattt의 매우 유명한 "NSHipster"에 대한 자세한 설명은 다음과 RawOptionsSetType같습니다. nshipster.com/rawoptionsettype
Klaas

답변:


258

스위프트 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")
}

스위프트 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")
}

스위프트 1.2

(스위프트에 의해 수입 된 목표 - C 옵션을 보면 UIViewAutoresizing예를 들어,), 우리는 옵션이로 선언 된 것을 볼 수 있습니다 struct해당 프로토콜을 준수 RawOptionSetType차례에 부합 함을 선언에서에 _RawOptionSetType, Equatable, RawRepresentable, BitwiseOperationsType,와 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) }
}

이제 MyOptionsApple 문서에 설명 된 것처럼 이 새로운 옵션 세트를 처리 할 수 있습니다. 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. 당신은 또한 함수를 정의 할 필요가 없습니다, 관련 기능이 이미 정의되어 있습니다 RawOptionSet(예를 들어 S func |<T : RawOptionSet>(a: T, b: T) -> T)
데이비드 로손

고마워, 함수에 대한 좋은 점-컴파일러가 나머지 프로토콜 적합성을 갖지 않았을 때 컴파일러가 불평한다고 생각합니다. 어떤 문제가 UInt있었습니까? 그것은 나를 위해 잘 작동합니다.
Nate Cook

2
구조체 대신 열거 형을 사용하는 솔루션이 있습니까? objective-c와 호환 될 수 있어야합니다.
jowie

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

1
이 경우 Apple의 문서 는 정말 좋습니다.
Mr Rogers

12

Xcode 6.1 베타 2는 RawOptionSetType프로토콜 을 일부 변경했습니다 (이 Airspeedvelocity 블로그 항목Apple 릴리스 정보 참조). ).

Nate Cooks 예제를 기반으로 업데이트 된 솔루션이 있습니다. 다음과 같이 자신의 옵션 세트를 정의 할 수 있습니다.

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스타일 유형을 새 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 용으로 만들어졌으며 그 이후로 업데이트되지 않았기 때문일 수 있습니다.
그레고리 히 글리

실제로 Swift 2.0 버전으로 작업하고 있습니다.
그레고리 히 글리

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

우리가 필요한 유일한 기능이 옵션을 결합 |하고 결합 된 옵션에 특정 옵션이 포함되어 있는지 확인 하는 방법 이라면 &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

복합 옵션을 결합 할 수 있는지 궁금해하는 다른 사람을 위해 추가 예를 게시하십시오. 당신은 할 수 있고, 당신이 좋은 오래된 비트 필드에 익숙하다면 기대하는 것처럼 결합합니다 :

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

다른 사람은 그것을 언급하지 않았으며 약간의 땜질 후에 난처하게 들었습니다. 그러나 스위프트 세트는 상당히 잘 작동하는 것 같습니다.

비트 마스크가 실제로 무엇을 나타내는 지에 대해 (벤 다이어그램으로 생각할 수 있습니까?) 생각하면 비어있는 세트 일 수 있습니다.

물론, 첫 번째 원칙에서 문제에 접근 할 때 비트 연산자의 편의성을 잃지 만 가독성을 향상시키는 강력한 세트 기반 방법을 얻습니다.

예를 들어 내 땜질은 다음과 같습니다.

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)등 또는 악화 1, 2, 16384등 또는 일부 진수 변화 한 먼저의 비트를 정의 할 수 enum, 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

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

신속한 3 사용에서 옵션 세트 유형 사용 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
이것은 이 답변 에서 이미 다루고 있습니다.
Pang
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.