Switch statement for imported NS_OPTIONS (RawOptionSetType) in Swift?

我的梦境 提交于 2019-12-02 06:09:44

问题


The switch statement in Swift is so much more expressive. I'm wondering if this might be possible:

Lets look at UIViewAutoresizing for example. It's defined in Objective-C as follows:

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
    UIViewAutoresizingNone                 = 0,
    UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
    UIViewAutoresizingFlexibleWidth        = 1 << 1,
    UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
    UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
    UIViewAutoresizingFlexibleHeight       = 1 << 4,
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};

I can use it in Swift like an enum:

let foo = UIViewAutoresizing([.FlexibleHeight, .FlexibleTopMargin])

Is it possible to use a switch statement instead of multiple if-statements?

if foo & UIViewAutoresizing.FlexibleHeight != nil {

}

if foo & UIViewAutoresizing.FlexibleWidth != nil {

}

if foo & UIViewAutoresizing.FlexibleTopMargin != nil {

}

Something like this pseudo code:

switch foo { // ** THIS IS PSEUDO CODE AND WILL NOT COMPILE **

case & .FlexibleHeight:
    println("height")

case & .FlexibleWidth:
    println("width")

case & .FlexibleTop:
    println("top")

}

回答1:


I was frustrated enough about this problem that I wrote a Bitmask<T> class that can handle these use cases. The code is up on Github: brynbellomy/SwiftBitmask

It allows you to do stuff like this with any kind of object as your underlying type (here I'm using an enum):

enum MonsterAttributes : IBitmaskRepresentable, IAutoBitmaskable {
    case Big, Ugly, Scary

    static var autoBitmaskValues : [MonsterAttributes] = [.Big, .Ugly, .Scary,]
    var  bitmaskValue: UInt16  { return AutoBitmask..autoBitmaskValueFor(self) }
    init(bitmaskValue: UInt16) { self = AutoBitmask.autoValueFromBitmask(bitmaskValue) }
}

// various ways to initialize
let option : MonsterAttributes = .Ugly

let bitmaskOfOption        = Bitmask(option)
let anotherBitmaskOfOption = |MonsterAttributes.Ugly // same as bitmaskOfOption

let orWithVar = option | .Big                 // == Bitmask<MonsterAttributes> with a bitmaskValue of 1 | 2
let simpleOr  = MonsterAttributes.Big | .Ugly // == Bitmask<MonsterAttributes> with a bitmaskValue of 1 | 2

// getting the raw integral bitmask value
let simpleOrValue = simpleOr.bitmaskValue                        // == UInt16(1 | 2)
let orValue       = (MonsterAttributes.Big | .Ugly).bitmaskValue // == UInt16(1 | 2)

// implements BooleanType
if simpleOr & .Ugly             { /* this code will execute */ }

// supports pattern matching operator
if simpleOr ~= .Ugly            { /* this code will execute */ }
if simpleOr ~= (.Ugly | .Scary) { /* this code will execute */ }

... and all you have to do is implement a one-property protocol.

I'm really curious if anyone has any feedback on or ideas for the code, so please leave an issue in the queue if you think of anything!




回答2:


I tried several hours yesterday and today to make this work with switch — no success.

The reason is that in this particular case we need to test against several cases. In Swift we need to use the fallthrough key word. But we are not allowed to fall through to the next case if that next case uses a variable, there for we cannot use the case let where statement, as shown here:

switch foo {
case let x where x & .FlexibleHeight != nil:
    println("height")
case let x where x & .FlexibleWidth != nil:
    println("width")
case let x where x & .FlexibleTopMargin != nil:
    println("top margin")
default:
    println("no")
}

This will break out once a case triggered. But

switch foo {
case let x where x & .FlexibleHeight != nil:
    println("height")
    fallthrough

case let x where x & .FlexibleWidth != nil:
    println("width")
    fallthrough

case let x where x & .FlexibleTopMargin != nil:
    println("top margin")
default:
    println("no")
}

does not work for the reason described above.


I'd go with a clear if statement, like

let foo = UIViewAutoresizing.FlexibleLeftMargin | UIViewAutoresizing.FlexibleHeight

let width = foo & UIViewAutoresizing.FlexibleWidth;
let height = foo & UIViewAutoresizing.FlexibleHeight;

if width == .FlexibleWidth {
    println("width")
}

if height == .FlexibleHeight {
    println("height")
}

or

let foo = UIViewAutoresizing.FlexibleLeftMargin | UIViewAutoresizing.FlexibleHeight

let usesFlexibleWidth = (foo & UIViewAutoresizing.FlexibleWidth) != nil;
let usesFlexibleHeight = (foo & UIViewAutoresizing.FlexibleHeight) != nil;

if usesFlexibleWidth {
    println("width")
}

if usesFlexibleHeight {
    println("height")
}



回答3:


You definitely can, although it's a little more convoluted now that RawOptionSetType doesn't implement the BooleanType protocol.

switch foo {
case let x where x & .FlexibleHeight != nil:
    println("height")
case let x where x & .FlexibleWidth != nil:
    println("width")
case let x where x & .FlexibleTopMargin != nil:
    println("top margin")
default:
    println("no")
}

Note that this stops on the first condition that matches! So it really is another form of this:

if foo & .FlexibleHeight != nil {
    println("height")
} else if foo & .FlexibleWidth != nil {
    println("width")
} else if foo & .FlexibleTopMargin != nil {
    println("top margin")
}



回答4:


Another solution that I came up with is this:

let foo = /* .. your value to test .. */

let allCases = [UIViewAutoresizing.FlexibleHeight, UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleTopMargin]

for oneCase in allCases {
    switch oneCase & foo {

    case UIViewAutoresizing.FlexibleHeight:
        println("height")

    case UIViewAutoresizing.FlexibleWidth:
        println("width")

    case UIViewAutoresizing.FlexibleTopMargin:
        println("top")

    default:
        break
    }
}


来源:https://stackoverflow.com/questions/25388065/switch-statement-for-imported-ns-options-rawoptionsettype-in-swift

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!