Reputation: 361
I am implementing a function called ofType
which filters out all the elements of the given type.
Here are my codes:
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.
In Objc, I can use isKindOf()
to check whether an object is an instance of the class or the subclass. There are similar operations in swift is
and as
, but the type after them should be a static type, not a dynamic type value (e.g. I can write is Mammal
, but not is Mammal.self
).
I cannot use the type parameter T
either because, in this example, the T
equals to Animal
, which is not what I want.
Do you have any idea about how to implement this function?
Upvotes: 6
Views: 2197
Reputation: 86691
This works. Just use as?
inside flatMap
. If the animal can be cast, it will be returned otherwise nil
is returned and flatMap
throws it away
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
If you explicitly type the output array, the compiler can infer T from the context, otherwise you pass T's type as a parameter but don't use it in the function.
If you want to be able to dynamically check the type i.e. you don't know the type to filter at compile time, you can use mirrors as it turns out. Here's a solution which is a bit clunky but it does work:
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)")
}
Prints:
[__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]
Edit Following Sam's suggestion in the comments, it occurred to me that the above method is best put in a protocol extension.
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
}
}
Then you can add the capability to any Swift type by making it conform to the protocol.
class Animal: TypeCheckable { ... }
extension String: TypeCheckable {}
Upvotes: 5
Reputation: 10105
You might use the reflection to find all the items that are compatible with the metatype, doing so:
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
Alternatively, with the following code I am using the operator is
and I am passing directly Mammal.self
to the
function 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
Upvotes: 2
Reputation: 80951
Personally, I think @JeremyP's suggestion to use Mirror
is the best; though I would make a couple of tweaks to it:
/// 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
}
Here we're using sequence(first:next:)
to create a sequence of metatypes from the dynamic type of x
through any superclass metatypes it might have (probably the first use of the function I've seen that doesn't look awful :P). In addition, we're falling back to doing an as?
cast when we know we're not doing a class-to-class cast, which allows the function to also work with protocol metatypes.
Then you can simply say:
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]
Another option, assuming you're on an Apple platform (i.e have access to the Objective-C runtime), is to use the the Objective-C metaclass method isSubclass(of:)
in order to check if a given metatype is equal, or is a subclass of another:
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
}
This works because on Apple platforms, Swift classes are built on top of Obj-C classes – and therefore the metatype of a Swift class is an Obj-C metaclass object.
Upvotes: 3
Reputation: 639
The isKindOf()
method is also available in Swift, as it is a method of the NSObjectProtocol
. So what you really need to do is subclass NSObject
for your declaration of Animal.
NOTE: The is kind of method is renamed to isKind(of: Type)
in swift.
should be as simple as
class Animal: NSObject {}
Now, all that is left, is to get around the problem that not all arrays will have elements that are a subclass of NSObject
or conform to NSObjectProtocol
.
To fix that we add a where
clause in the declaration of the swift extension.
It should now look like
extension Array where Element: NSObjectProtocol
Putting it all together, the final code should be similar to
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)
Upvotes: 1