Swift generics: тип возврата по типу параметра
Скажем, у меня есть набор объектов, наследуемых от общего суперкласса (в этом случае это предпочтительнее протоколов):
class ObjectSuperClass {
type: ObjectType
}
class ObjectClass1: ObjectSuperClass {
type = .Type1
}
class ObjectClass2: ObjectSuperClass {
type = .Type2
}
Я ищу создать общую функцию поиска, например:
func objectsOfType<T: ObjectSuperClass>(T.class, otherFilter: Any?) -> [T]
Что можно использовать для поиска данного подтипа, возвращая более конкретный массив результатов:
let result = objectsOfType(ObjectClass2.class, otherFilter: nil) -> [ObjectClass2]
(pseudo-swift)
Я чувствую, что это то, что может помочь генерик, но не может видеть, где должны быть помещены ограничения. Возможно ли это?
Ответы
Ответ 1
Ну, замечательно, это работает...
func filterType<T>(list: [AnyObject]) -> [T]
{
return list.filter{ $0 is T }.map{ $0 as! T }
}
... при условии, что вы присваиваете результат чему-то, что было явно напечатано, как в следующем примере:
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 выводится из запрошенного типа возвращаемого значения. Сама функция фильтрует исходный массив на основе того, имеют ли элементы требуемый тип, а затем принудительно приводит отфильтрованные результаты к правильному типу.
Если вам нужен "дополнительный фильтр", вам не нужно явно вводить результаты, если T может быть выведен из вашей функции дополнительного фильтра.
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>
РЕДАКТИРОВАТЬ
Более гладкая версия функции filterType
будет использовать flatMap()
func filterType<T>(list: [Any]) -> [T]
{
return list.flatMap{ $0 as? T }
}
РЕДАКТИРОВАТЬ 2
Flatmap не рекомендуется для дополнительных функций, так как Swift 4. что-то, используйте compactMap
func filterType<T>(list: [Any]) -> [T]
{
return list.compactMap{ $0 as? T }
}
Ответ 2
Это самое близкое приближение, которое я могу придумать:
func objectsOfType<T: ObjectSuperClass>(type type: T.Type) -> [T] {
// Just returns an array of all objects of given type
}
func objectsOfType<T: ObjectSuperClass>(type type: T.Type, predicate: T -> Bool) -> [T] {
// Uses predicate to filter out objects of given type
}
Использование:
let bar = objectsOfType(type: ObjectClass1.self)
let baz = objectsOfType(type: ObjectClass2.self) {
// Something that returns Bool and uses $0
}
Технически вы также можете пойти без аргумента type
в приведенном выше примере, но тогда вам нужно будет явно вводить приемники (bar
и baz
в приведенном выше примере), чтобы Swift мог правильно выводить типы для вы и используете правильную версию общей функции.
Ответ 3
Вы можете реализовать эту функцию следующим образом:
func objectsOfType<T: ObjectSuperClass>(objects: [ObjectSuperClass], subclass: T.Type, otherFilter: (T->Bool)?) -> [T] {
if let otherFilter = otherFilter {
return objects.filter{$0 is T && otherFilter($0 as! T)}.map{$0 as! T}
} else {
return objects.filter{$0 is T}.map{$0 as! T}
}
}
Пример использования:
objectsOfType(arrayOfObjects, subclass: ObjectClass1.self, otherFilter: nil)
Обратите внимание, что я не поклонник принудительного каста, однако в этом случае это не должно вызывать проблем.
Или более подробный вариант функции с одним менее принудительным нажатием:
func objectsOfType<T: ObjectSuperClass>(objects: [ObjectSuperClass], subclass: T.Type, otherFilter: (T->Bool)?) -> [T] {
return objects.filter({object in
if let object = object as? T {
if let otherFilter = otherFilter {
return otherFilter(object)
} else {
return true
}
} else {
return false
}
}).map({object in
return object as! T
})
}