2016-01-04 5 views
8

Sagen wir, ich habe eine Sammlung von Objekten aus einem gemeinsamen übergeordneten Klasse erben (dies ist vorzuziehen Protokolle in diesem Fall):Swift Generika: Rückgabetyp auf Basis von Parametertyp

class ObjectSuperClass { 
    type: ObjectType 
} 
class ObjectClass1: ObjectSuperClass { 
    type = .Type1 
} 
class ObjectClass2: ObjectSuperClass { 
    type = .Type2 
} 

Ich suche eine generisch zu schaffen Suchfunktion wie folgt aus:

func objectsOfType<T: ObjectSuperClass>(T.class, otherFilter: Any?) -> [T] 

die verwendet werden könnten für einen bestimmten Untertyp zu suchen, eine spezifischere Array von Ergebnissen der Rückkehr:

let result = objectsOfType(ObjectClass2.class, otherFilter: nil) -> [ObjectClass2] 
(pseudo-swift) 

ich so das Gefühl, irgendwo Generika könnte helfen, aber kann nicht sehen, wo Einschränkungen platziert werden soll. Ist es möglich?

Antwort

11

Nun bemerkenswert das funktioniert ...

func filterType<T>(list: [AnyObject]) -> [T] 
{ 
    return list.filter{ $0 is T }.map{ $0 as! T } 
} 

... vorausgesetzt Sie das Ergebnis etwas zuweisen, die explizit eingegeben, wie im folgenden Beispiel wurde:

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(unfilteredList) 
print("\(filteredList1)") // <t 1> 

let filteredList2: [ObjectClass2] = filterType(unfilteredList) 
print("\(filteredList2)") // <t 2> 

let filteredList3: [ObjectSuperClass] = filterType(unfilteredList) 
print("\(filteredList3)") // [<t 1>, <t 2>, <Who knows>] 

T inferred ist jeweils aus dem angeforderten Rückgabetyp. Die Funktion selbst filtert das ursprüngliche Array basierend darauf, ob die Elemente des gewünschten Typs sind, und dann Kraft wirft die gefilterten Ergebnisse auf den richtigen Typ.


Falls Sie ein „Extra-Filter“ Sie müssen nicht explizit um die Ergebnisse zu geben, solange T aus dem zusätzlichen Filterfunktion abgeleitet werden kann.

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> 
+0

Das ist schön, + 1.Out Neugier, könnte man die Verwendung von '.dynamicType' Eigenschaft sogar Art machen nur Objekte der Oberklasse aus? Z. B. 'filteredList3' in Ihrem Beispiel oben ergeben nur' [] '? (Zum Beispiel 'return list.filter {$ 0.dynamicType == T.self} .map {$ 0 wie!T} 'funktioniert nicht, aber etwas in diese Richtung?) – dfri

+0

@dfri möglicherweise. Ich habe es nicht überprüft. Normalerweise mache ich mir darüber keine Sorgen, denn wenn ich Objekte einer Unterklasse nicht so behandeln kann, als wären sie Objekte der Basisklasse, nehme ich das als einen Code-Geruch, der anzeigt, dass mein Klassendesign falsch ist. – JeremyP

+0

@dfri Eigentlich funktioniert es. Sie müssen nur den Parameter für den ersten Filterabschluss explizit als ein 'AnyObject' deklarieren, d. H.' Return list.filter {(anObj: AnyObject) -> Bool in einemObj.dynamicType == .self} .map {$ 0 as! T} ' – JeremyP

1

Dies ist die nächste Annäherung ich mit oben kommen kann:

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 
} 

Verbrauch:

let bar = objectsOfType(type: ObjectClass1.self) 

let baz = objectsOfType(type: ObjectClass2.self) { 
    // Something that returns Bool and uses $0 
} 

Technisch Sie auch ohne type Argument in der oben gehen, aber Sie dann explizit eingegeben Empfänger hat (bar und baz in dem obigen Beispiel) benötigen, so dass Swift richtig die Typen für Sie schließen kann und Verwenden Sie die richtige Version der generischen Funktion.

+0

Vielen Dank, wenn ich mehrere Antworten akzeptieren könnte. – Ben

0

Sie können die Funktion wie folgt implementieren:

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

Anwendungsbeispiel:

objectsOfType(arrayOfObjects, subclass: ObjectClass1.self, otherFilter: nil) 

Bitte beachte, dass ich kein Fan von Zwangsguss bin jedoch in diesem Szenario sollte es keine Probleme verursachen .

Oder die ausführlichere Version der Funktion, mit einer weniger gezwungener Besetzung:

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