7

I have this enum:

enum Animal {       
    case cat(CatModel)
    case dog(DogModel)
}

And an array of animals:

var animals: [Animal]

I need to find a Cat object in this array by a property that Dog doesn't have. litterBoxId for example.

let cat = animals.first(where: {$0.litterBoxId == 7})

This of course has an error:

Value of type 'MyViewController.Animal' has no member 'litterBoxId'

How can I accomplish this? I also tried

($0 as CatModel).litterBoxId

3 Answers 3

9

You can use pattern matching to accomplish this with 2 ways.

Using switch:

let cat = animals.first(where: {
    switch $0 {
    case .cat(let catModel) where catModel.litterBoxId == 7:
        return true
    default:
        return false
    }
})

or if:

let cat = animals.first(where: {
    if case .cat(let catModel) = $0, catModel.litterBoxId == 7 {
        return true
    }
    return false
})

Update: As @Alexander-ReinstateMonica mentioned in his commnet, it would be more appropriate to hide this logic behind a function like this:

extension Animal {
    func matches(litterboxID: Int) -> Bool {
        switch self {
        case .cat(let catModel) where catModel.litterBoxId == 7:
            return true
        default:
            return false
        }
    }
}

and then your code will be much cleaner:

let cat = animals.first(where: { $0.matches(litterboxID: 7) })
Sign up to request clarification or add additional context in comments.

4 Comments

I wouldn't recommend this, it's a total mess that obscures intent. I would suggest to at leasst extract these into functions on the enum.
@Alexander-ReinstateMonica so, a function in the enum that would take the array of enums and the litterBoxId as parameters? Did I understand you correctly?
At the most superficial level, you could introduce func litterboxID(of: Animal, matches: Int) -> Bool, and put your pattern matching logic in there, so you can just call animals.first(where: { litterboxID(of: Animal, matches: 7) }. Better yet, you should put access to those fields in the enum itself, as I mention in my answer (and the comments to it). Better better yet, use a protocol that can contain these fields (if applicable) and allows you easily to cast to get the rest
@Alexander-ReinstateMonica thank you. I update my answer with one of your suggestions.
1

This probably isn't a good use of enums, but here is how it could work:

struct CatModel {
    let litterBoxID: Int
}

struct DogModel {
    let litterBoxID: Int
}

enum Animal {       
    case cat(CatModel)
    case dog(DogModel)
    
    var litterBoxId: Int {
        switch self {
        case .cat(let cat): return cat.litterBoxID
        case .dog(let dog): return dog.litterBoxID
        }
    }
}

var animals: [Animal] = []
let cat = animals.first(where: { $0.litterBoxId == 7 })

You would be much better off using a protocol:

struct CatModel: Animal {
    let litterBoxID: Int
}

struct DogModel: Animal {
    let litterBoxID: Int
}

protocol Animal {
    var litterBoxID: Int { get }
}

var animals: [Animal] = []
let cat = animals.first(where: { $0.litterBoxID == 7 })

2 Comments

Part of the specification though was that DogModel doesn't have litterBoxID.
@soleil Then you can make the litterBoxId field optional, and write { $0.litterBoxID? == 7 }. Alternatively, you can make var catModel: Cat? and var dogModel: Dog?, and write { $0.cat?.litterBoxID == 7 }. In either case, the much more natural approach is to use a protocol (to contain whatever properties happen to be in common), and cast for the parts that are specific: { ($0 as? Cat)?.litterBoxID == 7 }.
0

You could add an extension that gives you back an array of CatModel

extension Array where Element == Animal {
var cats: [CatModel] {
    var filteredCats = [CatModel]()
    self.forEach { animal in
        switch animal {
        case .cat(let catModel): filteredCats.append(catModel)
        case .dog: break
        }
    }
    return filteredCats
}

}

let cat = animals.cats.first(where: {$0.litterBoxId == 7})

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.