Say I have a collection of objects inheriting from a common superclass (this is preferable to protocols in this case):
class ObjectSuperClass { type: ObjectType } class ObjectClass1: ObjectSuperClass { type = .Type1 } class ObjectClass2: ObjectSuperClass { type = .Type2 }
I'm looking to create a generic search function like this:
func objectsOfType<T: ObjectSuperClass>(T.class, otherFilter: Any?) -> [T]
Which could be used to search for a given sub-type, returning a more specific array of results:
let result = objectsOfType(ObjectClass2.class, otherFilter: nil) -> [ObjectClass2] (pseudo-swift)
I feel like this is somewhere generics could help, but cannot see where constraints should be placed. Is it possible?
Well remarkably this works...
func filterType<T>(list: [AnyObject]) -> [T] { return list.filter{ $0 is T }.map{ $0 as! T } }
...provided you assign the result to something that has been explicitly typed, as in the following example:
class ObjectSuperClass: CustomStringConvertible { let myType: String init(aString: String) { myType = aString } var description: String { return myType } } class ObjectClass1: ObjectSuperClass { init() { super.init(aString: "<t 1>") } } class ObjectClass2: ObjectSuperClass { init() { super.init(aString: "<t 2>") } } let unfilteredList: [AnyObject] = [ ObjectClass1(), ObjectClass2(), ObjectSuperClass(aString: "<Who knows>")] let filteredList1: [ObjectClass1] = filterType(list: unfilteredList) print("\(filteredList1)") // <t 1> let filteredList2: [ObjectClass2] = filterType(list: unfilteredList) print("\(filteredList2)") // <t 2> let filteredList3: [ObjectSuperClass] = filterType(list: unfilteredList) print("\(filteredList3)") // [<t 1>, <t 2>, <Who knows>]
T is inferred in each case from the requested return type. The function itself filters the original array based on whether the elements are of the required type and then force casts the filtered results to the correct type.
If you want an "extra filter" you don't need to explicitly type the results as long as T can be inferred from your extra filter function.
func extraFilterType<T>(list: [AnyObject], extraFilter: T -> Bool) -> [T] { return list.filter{ $0 is T }.map{ $0 as! T }.filter(extraFilter) } let filteredList = extraFilterType(unfilteredList){ (element : ObjectClass2) -> Bool in !element.description.isEmpty } print("\(filteredList)") // <t 2>
EDIT
A slicker version of the filterType
function would use flatMap()
func filterType<T>(list: [Any]) -> [T] { return list.flatMap{ $0 as? T } }
EDIT 2
Flatmap is deprecated for optionals, since Swift 4.something, use compactMap
func filterType<T>(list: [Any]) -> [T] { return list.compactMap{ $0 as? T } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With