2016-01-04 6 views
8

Dire che ho una collezione di oggetti che ereditano da una superclasse comune (questo è preferibile protocolli in questo caso):generici Swift: Tipo di ritorno in base al tipo di parametro

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

Sto cercando di creare un generico funzione di ricerca simili:

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

che potrebbero essere utilizzati per cercare un determinato sottotipo, restituendo un allineamento più preciso dei risultati:

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

Mi sembra che da qualche parte i generici possano aiutare, ma non riescano a vedere dove devono essere posizionati i vincoli. È possibile?

risposta

11

Bene notevolmente questo funziona ...

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

... a patto di assegnare il risultato a qualcosa che è stato esplicitamente digitato, come nel seguente esempio:

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 è dedotto in ogni caso dal tipo di reso richiesto. La funzione stessa filtra l'array originale in base al fatto che gli elementi siano del tipo richiesto e quindi impone il cast dei risultati filtrati al tipo corretto.


Se si desidera un "filtro di extra" non è necessario digitare in modo esplicito i risultati finché T si può dedurre dal vostro funzione di filtro in più.

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

Questo è bello, + 1.Out di curiosità, hai potuto fare uso di 'proprietà .dynamicType' a anche ordinare solo oggetti della superclasse? Ad esempio, 'filteredList3' nell'esempio sopra riportato per produrre solo' [] '? (Ad esempio: return list.filter {$ 0.dynamicType == T.self} .map {$ 0 as!T} 'non funziona, ma qualcosa del genere?) – dfri

+0

@dfri possibilmente. Non ho controllato Di solito non mi preoccupo perché, se non riesco a trattare oggetti di una sottoclasse come se fossero oggetti della classe base, lo prendo come un odore di codice che indica che il mio design di classe è sbagliato. – JeremyP

+0

@dfri In realtà funziona. Devi solo dichiarare esplicitamente il parametro per il primo filtro come un 'AnyObject' i.e. return list.filter {(anObj: AnyObject) -> Bool in anObj.dynamicType == T.self} .map {$ 0 as! T} ' – JeremyP

1

Questo è l'approssimazione più vicina che posso venire con:

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 
} 

Usage:

let bar = objectsOfType(type: ObjectClass1.self) 

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

Tecnicamente, si può anche andare senza type argomento a quanto sopra, ma poi si sarà necessario avere ricevitori digitati in modo esplicito (bar e baz nell'esempio precedente) in modo che Swift possa correttamente dedurre i tipi per te e utilizzare la versione corretta della funzione generica.

+0

Grazie, se potessi accettare più risposte lo farei. – Ben

0

È possibile implementare la funzione in questo modo:

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

Esempio di utilizzo:

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

Si noti che io non sono un fan di fusione forzata, tuttavia in questo scenario non dovrebbe causare problemi .

Oppure, la versione più dettagliata della funzione, con uno in meno fusione forzata:

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