Swift - 在带有关联值的枚举数组中查找对象

4

I have this enum:

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

还有一组动物:

var animals: [Animal]

我需要通过 Dog 没有的属性,例如 litterBoxId,在此数组中查找 Cat 对象。
let cat = animals.first(where: {$0.litterBoxId == 7})

这当然是一个错误:

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

我该如何实现这个?我也尝试过。
($0 as CatModel).litterBoxId
3个回答

5
你可以使用2种方法中的任意一种来实现这个功能,其中之一是使用模式匹配。
使用switch语句:
let cat = animals.first(where: {
    switch $0 {
    case .cat(let catModel) where catModel.litterBoxId == 7:
        return true
    default:
        return false
    }
})

或者如果:

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

更新:如@Alexander-ReinstateMonica在评论中提到的那样,更合适的做法是将此逻辑隐藏在一个类似于以下函数的函数后面:

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

然后你的代码将会更加简洁:

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

我不建议这样做,它会完全混淆意图。我建议至少将它们提取到枚举的函数中。 - Alexander
@Alexander-ReinstateMonica 所以,枚举中的一个函数将接受枚举数组和“litterBoxId”作为参数?我理解得对吗? - gcharita
1
在最表面的层面上,你可以引入 func litterboxID(of: Animal, matches: Int) -> Bool,并将你的模式匹配逻辑放在那里,这样你就可以只调用 animals.first(where: { litterboxID(of: Animal, matches: 7) }。更好的做法是,像我在答案中提到的那样(以及它的注释),把访问这些字段的权限放在枚举本身中。更好的方法是使用一个协议,它可以包含这些字段(如果适用)并允许您轻松地进行强制转换以获取其他内容。 - Alexander
@Alexander-ReinstateMonica 谢谢。我用你的建议更新了我的答案。 - gcharita

1
这可能不是枚举的一个好应用,但以下是它的可能运作方式:
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 })

你最好使用协议:

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

规范的一部分是,DogModel 没有 litterBoxID。 - soleil
1
@soleil 然后你可以将 litterBoxId 字段设为可选的,并写成 { $0.litterBoxID? == 7 }。或者,你可以将 var catModel: Cat?var dogModel: Dog?,并写成 { $0.cat?.litterBoxID == 7 }。在任何情况下,更自然的方法是使用协议(包含任何共同的属性),并转换为特定的部分:{ ($0 as? Cat)?.litterBoxID == 7 } - Alexander

0
你可以添加一个扩展,它会返回一个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})

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接