Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mapping swift enum with associated values

Let say we have an enum with associated value types. In the example below the two value types are simple object that hold an image and a url to share.

enum Content {
  case Image(ShareableImage)
  case Video(ShareableVideo)
}

Now let's have an array of video and image cases.

let media: [Content] = [*a lot of enum cases inside here*]  

All the code above so far cannot be changed in any way in the codebase, I need to work with it.


Here starts my problem:

Let's filter the array with media to only image cases

    let imageOnlyCases: [Content] = media.filter { item -> Bool in

        switch item {
        case .Image: return true
        default: return false
        }
    }

Next step, I want to get from array of enum to an array of their associated values

[Content] -> [ShareableImage] by using map.

so I do this

    let shareablemages = imageOnlyCases.map { imageCase -> ShareableImage in

        switch imageCase {

        case .Image(let image): return image
        default: return  WHAT TO DO HERE?
        }
    }

You see, I have a problem with return type..I know that the enum cases are all .Image..and I want a simple map. But the swift syntax is not helping me.

Any ideas?

like image 764
Earl Grey Avatar asked Feb 26 '16 11:02

Earl Grey


2 Answers

You could return image for case .Image, and nil otherwise, within a .flatMap operation (to "filter" out nil entries):

/* Example */
enum Foo {
    case Bar(Int)
    case Baz(Int)
}

let foo: [Foo] = [.Bar(1), .Bar(9),. Baz(3), .Bar(39), .Baz(5)]

/* 1. using 'switch' */
let barOnlyValues: [Int] = foo.flatMap {
    switch $0 {
    case .Bar(let val): return val
    case _: return nil
    }}

/* 2. alternatively, as pointed out in MartinR:s answer; 
      as you're only looking for a single case, the alternative
      'if case let' clause could be preferred over 'switch':     */
let barOnlyValuesAlt: [Int] = foo.flatMap {
    if case let .Bar(val) = $0 { return val }
    else { return nil }}                               

print(barOnlyValues) // [1, 9, 39]

Applied to your use case: note that you needn't perform the filtering to create the imageOnlyCases array, as you can apply the above directly on the media array:

/* 1. using switch */
let shareableImages : [ShareableImage] = media.flatMap {
    switch $0 {
    case .Image(let image): return image
    case _: return nil
    }}

/* 2. 'if case let' alternative, as per MartinR:s suggestion */
let shareableImagesAlt : [ShareableImage] = media.flatMap {
    if case let .Image(image) = $0 { return image }
    else { return nil }}

Disclaimer: I cannot verify your specific use case in practice as I don't have access to the ShareableImage class/struct.

(Thanks @MartinR for advice that .map{ ... }.flatMap{ ... } can be simplified to just .flatMap{ ... }).

like image 133
dfrib Avatar answered Oct 03 '22 02:10

dfrib


If it is guaranteed that only the .Image case can occur then you can call fatalError() in all other cases:

let shareableImages = imageOnlyCases.map { imageCase -> ShareableImage in

    if case let .Image(image) = imageCase {
        return image
    } else {
        fatalError("Unexpected content")
    }
}

fatalError() causes the program to terminate immediately. It is only meant for situations that "cannot occur", i.e. to find programming errors.

It satisfies the compiler because the function is marked as @noreturn.

If you cannot make that guarantee then use flatMap() as suggested in the other answer.

Note also that you can use if case here with a pattern instead of switch/case.

like image 38
Martin R Avatar answered Oct 03 '22 02:10

Martin R