2014-10-09 18 views
17

Sto provando a trasmettere e/o generare una variabile in base al tipo generico specificato. Capisco che non ci sia cancellazione di tipo in swift, ma non sembra che i generici conservino un tipo diverso dalle condizioni specificate del generico, ad es. conforme a una classe base. Sembra che tutto quello che posso lanciare o inizializzare sia la classe base. La cosa ancora più strana è quando mi trovo nel debugger i appare generici hanno un RawPointer alla classe corretta e anche le variabili appaiono come se fossero del tipo giusto:Generici Swift che non conservano il tipo

EDIT:

Come di Xcode 6.1 questo è ancora un problema (codice di cortesia semplificata di Gregory Higley):

class BaseClass { 
    func printme() -> Void { 
     println("I am BaseClass") 
    } 
} 

class DerivedClass : BaseClass { 
    override func printme() -> Void { 
     println("I am DerivedClass") 
    } 
} 

class Util<T: BaseClass> { 
    func doSomething() { 
     var instance = T() 
     instance.printme() 
    } 
} 

var util = Util<DerivedClass>() 
util.doSomething() 

stampa ancora fuori "io sono BaseClass"

piacerebbe anche notare che init richiesta {} nelle clas di base s non funziona più.

enter image description here

+0

È necessario modificare la risposta corretta a rintaro. –

+0

@rfrittelli - In realtà la risposta corretta è quella di JeremyP qui sotto. Ho confermato che funziona e che la risposta di rintaro non funziona più nell'ultimo XCode. – Lee

+0

Qual era il tuo output originale (sarebbe stato d'aiuto)? A partire da 6.1, entrambi i test producono lo stesso risultato, che è previsto. E puoi anche indicare la tua domanda? –

risposta

15

Questo codice funziona come previsto.

class BaseClass { 

    required init() {} // <-- ADDED THIS 

    func printme() -> Void { 
     println("I am BaseClass") 
    } 
} 

class DerivedClass : BaseClass { 
    override func printme() -> Void { 
     println("I am DerivedClass") 
    } 
} 

class Util<T: BaseClass> { 
    func doSomething() { 
     var instance = T() 
     instance.printme() 
    } 
} 

var util = Util<DerivedClass>() 
util.doSomething() 

base di codice vengono rubati dalla risposta @GregoryHigley :)

Marcatura init() {} come required ha fatto la cosa. Questo garantisce init() è l'inizializzatore designato della classe derivata ANY da BaseClass.

Senza di essa, si può fare sottoclasse illegale come:

class IllegalDerivedClass : BaseClass { 
    var name:String 

    init(name:String) { 
     self.name = name 
     super.init() 
    } 

    override func printme() -> Void { 
     println("I am DerivedClass") 
    } 
} 

var util = Util<IllegalDerivedClass>() 
util.doSomething() 

sai che questo non funziona perché IllegalDerivedClassdoesn't inherit init() initializer.

Penso che questa sia la ragione del tuo problema.

In ogni caso, di chi è la colpa?

  • Compiler dovrebbe mettere in guardia circa ambiguità.
  • Runtime deve provare a inizializzare DerivedClass() come specificato con T.
  • Il debugger dovrebbe mostrare instance è un'istanza di BaseClass così com'è.

aggiunto:

Come di Xcode 6.1 GM 2, sembra, è necessario più lavoro. (In aggiunta a required init() {})

class Util<T: BaseClass> { 
    let theClass = T.self // store type itself to variable 

    func doSomething() { 
     var instance = theClass() // then initialize 
     instance.printme() 
    } 
} 

Non ho assolutamente idea del perché abbiamo bisogno di questo, quello che sta succedendo X (

aggiunto: 2014/10/18

Ho trovato questo funziona anche:

func doSomething() { 
     var instance = (T.self as T.Type)() 
     instance.printme() 
    } 

Aggiunto: 2015/02/10

Come di Xcode Versione 6.3 (6D520o)/Swift 1.2

Non abbiamo più bisogno (T.self as T.Type)() mod. Solo T() funziona finchè T ha l'inizializzatore required init().

class Util<T: BaseClass> { 
    func doSomething() { 
     var instance = T() 
     instance.printme() 
    } 
} 
+0

Questo dovrebbe essere contrassegnato come la risposta corretta. Tristemente, però, lo considero ancora un bug, poiché viola il principio di minima sorpresa. Farlo funzionare correttamente richiede conoscenze specialistiche che non dovrebbero essere necessarie. –

+0

Grande bella risposta. Ma sono d'accordo che è ancora un bug. – rfrittelli

+0

-1 Poiché non funziona con l'ultimo XCode (6.1 GM2), penso che la risposta di @ JeremyP funzioni. – Lee

6

ho creato una versione semplificata del codice come segue:

class BaseClass { 
    func printme() -> Void { 
     println("I am BaseClass") 
    } 
} 

class DerivedClass : BaseClass { 
    override func printme() -> Void { 
     println("I am DerivedClass") 
    } 
} 

class Util<T: BaseClass> { 
    func doSomething() { 
     var instance = T() 
     instance.printme() 
    } 
} 

var util = Util<DerivedClass>() 
util.doSomething() 

Questo distilla il problema alla sua essenza. Ci si potrebbe aspettare che util.doSomething() stampi "I am DerivedClass", ma stampa "I am BaseClass" ogni volta. Questo deve essere un bug, perché nessun sistema di tipo razionale funzionerebbe in questo modo.

Penso che dovresti archiviarlo con Apple come un bug.

+1

Sì mi dispiace per il mio esempio confuso, ma questo è esattamente quello che stavo pensando. Grazie e lo farò. – rfrittelli

+0

Quando si archivia il bug, si prega di utilizzare il mio esempio anziché il proprio, poiché è molto più semplice. (Non è necessario il credito. Dopo aver scoperto il bug.) –

+0

Ho usato il tuo :). Grazie ancora! – rfrittelli

4

Il problema è var instance = T() Gli inizializzatori non sono virtuali, quindi l'istanza viene sempre eseguita con BaseClass()*. Il codice seguente utilizza una funzione di classe per aggirare il problema:

class BaseClass { 
    func printme() -> String { 
     return "I am BaseClass" 
    } 
    class func makeInstance() -> BaseClass 
    { 
     return BaseClass() 
    } 
} 

class DerivedClass : BaseClass { 
    override class func makeInstance() -> BaseClass 
    { 
     return DerivedClass() 
    } 

    override func printme() -> String { 
     return "I am DerivedClass" 
    } 
} 

class Util<T: BaseClass> { 
    func doSomething() -> String { 
     var instance = T.makeInstance() 
     return instance.printme() 
    } 
} 

var util = Util<DerivedClass>() 
println("\(util.doSomething())") 

ho cambiato l'attuazione di printme() solo perché il codice originale non è stato stampato nulla in un parco giochi per qualche motivo.

* Penso che questo sia ancora un bug.

+1

Ci avevo pensato ... ma a me sembra ancora rotto. Perché il tipo non può inferire se stesso e chiamare init su se stesso? – rfrittelli

+0

@rfrittelli Penso che sia anche un bug. Sto solo spiegando qual è il bug IMHO – JeremyP

+0

Sì, non si dovrebbe fare a meno di saltare questo tipo di cerchi per farlo. Viola il "principio di minima sorpresa". Ho molte critiche al sistema di tipo di Swift, specialmente dove riguarda i generici e le estensioni. Sembra che ci siano molti "dossi di velocità". –

1

Ho avuto un problema simile. È necessario aggiungere l'inizializzatore required e let realType = T.self e sostituire T() con realType().

class BaseClass { 
    required init() {} 
    func printme() -> Void { 
     println("I am BaseClass") 
    } 
} 

class DerivedClass : BaseClass { 
    override func printme() -> Void { 
     println("I am DerivedClass") 
    } 
} 

class Util<T: BaseClass> { 
    func doSomething() { 
     let realType = T.self // that's it 
     var instance = realType() 
     instance.printme() 
    } 
} 

var util = Util<DerivedClass>() 
util.doSomething() 
Problemi correlati