programing tip

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

itbloger 2020. 7. 4. 11:14
반응형

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


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

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

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

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


스위프트 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 수정.


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

설명서의 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]
}

여기에서 찾을 수 있습니다


Swift 2 (현재 Xcode 7 베타의 일부로 베타)에서 NS_OPTIONS스타일 유형을 새 OptionSetType유형의 하위 유형으로 가져옵니다 . 새로운 프로토콜 확장 기능과 OptionSetType표준 라이브러리에서 구현 된 방식 덕분에 OptionsSetType가져온 NS_OPTIONS스타일 유형 과 동일한 기능 및 메소드를 확장 하고 가져 오는 고유 한 유형을 선언 할 수 있습니다 .

그러나 이러한 함수는 더 이상 비트 산술 연산자를 기반으로하지 않습니다. C에서 비 독점 부울 옵션 세트를 사용하려면 필드에서 마스킹 및 비트를 처리해야합니다. 실제로, 옵션 세트 는 고유 한 아이템의 집합입니다. 따라서 배열 리터럴 구문에서 생성,와 같은 쿼리 ,로 마스킹 OptionsSetTypeSetAlgebraType프로토콜 에서 모든 메소드를 가져 옵니다 (어떤 멤버십 테스트에 어떤 재미있는 캐릭터를 사용해야하는지 기억할 필요가 없습니다!)containsintersection


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

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

등등. 실제 비트는 여기서 뒤집 히지 않습니다. 이들은 불투명 한 값에 대해 설정된 작업입니다. 여기서 요점을 찾을 수 있습니다 .


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

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

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

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

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

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

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

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

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

I find this nice because I feel it comes from a first principles approach to the problem -- much like Swift -- rather than trying to adapt C-style solutions.

Would also like to hear some Obj-C use cases that would challenge this different paradigm, where the integer raw values still shows merit.


In order to avoid hard coding the bit positions, which is unavoidable when using (1 << 0), (1 << 1), (1 << 15) etc. or even worse 1, 2, 16384 etc. or some hexadecimal variation, one could first defines the bits in an enum, then let said enum do the bit ordinal calculation:

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

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

And if one needs more just add a computed property.

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

re: Sandbox and bookmark creations using option sets with several options

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

solution to needing to combine options for creations, useful when not all options are mutually exclusive.


Nate's answer is good but I would make it DIY, like so:

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

Use an Option Set Type, in swift 3 use 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]
}

참고URL : https://stackoverflow.com/questions/24066170/how-to-create-ns-options-style-bitmask-enumerations-in-swift

반응형