我正在实现一个名为ofType
的函数,它过滤掉给定类型的所有元素。
以下是我的代码:
class Animal {}
class Mammal: Animal {}
class Monkey: Mammal {}
class Pig: Mammal {}
class Human: Mammal {}
extension Array {
func ofType<T>(_ metatype: T.Type) -> [T] {
return flatMap { type(of: $0) == metatype ? $0 as? T : nil }
// return flatMap { $0 as? T } // This is not working as the T is always the static type of the parameter, which is Animal in this example.
// return flatMap { $0 as? metatype } // This is not working either because of the grammar restriction.
}
}
let animals = [Monkey(), Pig(), Human(), Mammal(), Animal()]
func animalType() -> Animal.Type {
return Mammal.self
}
animals.ofType(animalType()).count // returns 1, expect to be 4.
在Objc中,我可以使用isKindOf()
来检查对象是否是类或子类的实例。 swift is
和as
中有类似的操作,但它们之后的类型应该是静态类型,而不是动态类型值(例如,我可以写is Mammal
,但不能{{1} }})。
我无法使用类型参数is Mammal.self
,因为在此示例中,T
等于T
,这不是我想要的。
您对如何实现此功能有任何了解吗?
答案 0 :(得分:5)
这很有效。只需在as?
内使用flatMap
即可。如果动物可以施放,它将被返回,否则返回nil
并且flatMap
扔掉它
class Animal {}
class Mammal: Animal {}
class Monkey: Mammal {}
class Pig: Mammal {}
class Human: Mammal {}
extension Array {
func ofType<T>() -> [T]
{
return flatMap { $0 as? T }
}
}
let animals = [Monkey(), Pig(), Human(), Mammal(), Animal()]
let monkeys: [Monkey] = animals.ofType() // A one element array
let mammals: [Mammal] = animals.ofType() // A four element array
如果显式键入输出数组,编译器可以从上下文中推断T,否则将T的类型作为参数传递,但不要在函数中使用它。
如果您希望能够动态检查类型,即您不知道要在编译时过滤的类型,则可以使用镜像。这是一个有点笨重的解决方案,但确实有效:
class Animal
{
func isInstance(of aType: Any.Type) -> Bool
{
var currentMirror: Mirror? = Mirror(reflecting: self)
while let theMirror = currentMirror
{
if theMirror.subjectType == aType
{
return true
}
currentMirror = theMirror.superclassMirror
}
return false
}
}
class Mammal: Animal {}
class Monkey: Mammal {}
class Pig: Mammal {}
class Human: Mammal {}
let animals = [Monkey(), Pig(), Human(), Mammal(), Animal()]
for aType in [Animal.self, Mammal.self, Monkey.self]
{
let result = animals.flatMap { $0.isInstance(of: aType) ? $0 : nil }
print("\(result)")
}
打印:
[__lldb_expr_12.Monkey, __lldb_expr_12.Pig, __lldb_expr_12.Human, __lldb_expr_12.Mammal, __lldb_expr_12.Animal]
[__lldb_expr_12.Monkey, __lldb_expr_12.Pig, __lldb_expr_12.Human, __lldb_expr_12.Mammal]
[__lldb_expr_12.Monkey]
编辑在评论中提出Sam的建议后,我发现上述方法最好放在协议扩展中。
protocol TypeCheckable {}
extension TypeCheckable
{
func isInstance(of aType: Any.Type) -> Bool
{
var currentMirror: Mirror? = Mirror(reflecting: self)
while let theMirror = currentMirror
{
if theMirror.subjectType == aType
{
return true
}
currentMirror = theMirror.superclassMirror
}
return false
}
}
然后,您可以通过使其符合协议,将功能添加到任何Swift类型。
class Animal: TypeCheckable { ... }
extension String: TypeCheckable {}
答案 1 :(得分:2)
您可以使用反射来查找与元类型兼容的所有项目,这样做:
class Animal { }
class Mammal: Animal {}
class Monkey: Mammal {}
class Pig: Mammal {}
class Human: Mammal {}
extension Array {
func ofType<T>(_ metatype: T.Type) -> [T] {
return flatMap { item in
var mirror:Mirror? = Mirror(reflecting: item)
while let currentMirror = mirror {
mirror = currentMirror.superclassMirror
if currentMirror.subjectType == metatype {
return item as? T
}
}
return nil
}
}
}
let animals = [Monkey(), Pig(), Human(), Mammal(), Animal()]
func animalType() -> Animal.Type {
return Mammal.self
}
let result = animals.ofType(animalType())
print(result) // returns 4 items: Monkey, Pig, Human, Mammal
或者,使用以下代码我使用运算符is
,我直接将Mammal.self
传递给
函数ofType
:
class Animal {}
class Mammal: Animal {}
class Monkey: Mammal {}
class Pig: Mammal {}
class Human: Mammal {}
extension Array {
func ofType<T>(_ metatype: T.Type) -> [T] {
return flatMap { $0 is T ? $0 as? T : nil }
}
}
let animals = [Monkey(), Pig(), Human(), Mammal(), Animal()]
let result = animals.ofType(Mammal.self)
print(result) // returns 4 items: Monkey, Pig, Human, Mammal
答案 2 :(得分:2)
我个人认为@JeremyP's suggestion使用Mirror
是最好的;虽然我会对它进行一些调整:
/// Conditionally cast `x` to a given dynamic metatype value, taking into consideration
/// class inheritance hierarchies.
func conditionallyCast<T, U>(_ x: T, to destType: U.Type) -> U? {
if type(of: x) is AnyClass && destType is AnyClass { // class-to-class
let isCastable = sequence(
first: Mirror(reflecting: x), next: { $0.superclassMirror }
)
.contains { $0.subjectType == destType }
return isCastable ? (x as! U) : nil
}
// otherwise fall back to as?
return x as? U
}
这里我们使用sequence(first:next:)
从动态类型x
通过它可能具有的任何超类元类型创建一系列元类型(可能是我见过的第一次使用的函数)看起来很糟糕:P)。另外,当我们知道我们没有进行类到类的转换时,我们又回到了做as?
转换,这使得该函数也可以使用协议元类型。
然后你可以简单地说:
extension Sequence {
func ofType<T>(_ metatype: T.Type) -> [T] {
return flatMap { conditionallyCast($0, to: metatype) }
}
}
protocol P {}
class Animal {}
class Mammal: Animal {}
class Monkey: Mammal, P {}
class Pig: Mammal {}
class Human: Mammal, P {}
let animals = [Monkey(), Pig(), Human(), Mammal(), Animal()]
let animalType: Animal.Type = Mammal.self
print(animals.ofType(animalType)) // [Monkey, Pig, Human, Mammal]
print(animals.ofType(P.self)) // [Monkey, Human]
假设您在Apple平台上(即可以访问Objective-C运行时),另一个选择是使用Objective-C元类方法isSubclass(of:)
来检查给定的元类型是否为等于,或者是另一个的子类:
import Foundation
/// Conditionally cast `x` to a given dynamic metatype value, taking into consideration
/// class inheritance hierarchies.
func conditionallyCast<T, U>(_ x: T, to destType: U.Type) -> U? {
let sourceType = type(of: x)
if let sourceType = sourceType as? AnyClass,
let destType = destType as? AnyClass { // class-to-class
return sourceType.isSubclass(of: destType) ? (x as! U) : nil
}
// otherwise fall back to as?
return x as? U
}
这是有效的,因为在Apple平台上,Swift类是建立在Obj-C类之上的 - 因此Swift类的元类型是Obj-C元类对象。
答案 3 :(得分:0)
isKindOf()
方法在Swift中也可用,因为它是NSObjectProtocol
的一种方法。因此,您真正需要做的是为您的Animal声明创建子类NSObject
。
注意:这种方法在swift中重命名为isKind(of: Type)
。
应该像
一样简单class Animal: NSObject {}
现在,剩下的就是解决这个问题,即并非所有数组都包含NSObject
的子类或符合NSObjectProtocol
的元素。
为了解决这个问题,我们在swift扩展的声明中添加了一个where
子句。
现在应该看起来像
extension Array where Element: NSObjectProtocol
总而言之,最终的代码应该类似于
class Animal: NSObject {}
class Mammal: Animal {}
class Monkey: Mammal {}
class Pig: Mammal {}
class Human: Mammal {}
extension Array where Element: NSObjectProtocol {
func ofType<T: NSObjectProtocol>(_ metatype: T.Type) -> [T] {
return flatMap { $0.isKind(of: metatype) ? $0 as? T : nil }
}
}
let animals = [Monkey(), Pig(), Human(), Mammal(), Animal()]
func animalType() -> Animal.Type {
return Mammal.self
}
print(animals.ofType(animalType()).count)