2014-11-25 14 views
5

UPDATE: domanda e titolo sono stati riformulati in base alle modifiche apportate in risposta alla domanda iniziale.Impossibile chiamare l'inizializzatore per sottoclasse di tipo generico


Ho una classe base che implementa un metodo di classe generico per la creazione di nuove istanze. La logica semplificata di questo metodo di classe è la seguente

class MyBaseClass { 

    required init(_ record:MyRecordType) { 
     println("Entered MyBaseClass.init") 
     // Initialize base class from record 
    } 

    class func retrieveInstance<T:MyBaseClass>(name:String, callback:(T) ->()) { 
     let myRecord:MyRecordType = ... // Perform fetch to get a record for name 
     let result = (T.self as T.Type)(myRecord) // Code currently crashes with BAD_ACCESS at this line 
     callback(result) 
    } 
} 

ho quindi implementare una sottoclasse di questa classe di base con logica come la seguente

class MySubClass : MyBaseClass { 

    required init(_ record:MyRecordType) { 
     println("Entered MySubClass.init") 
     // Initialize subclass from record 
     super.init(record) 
    } 
} 

Avanti, provo a richiamare il metodo classe generica

Quando si crea un'istanza della classe, la riga contrassegnata da un commento che identifica la posizione di arresto anomalo, mi aspetto il metodo init da MySubClass a essere invocato. (Questa strana notazione si basa sulla soluzione di bug suggerita nelle risposte)

Invece di chiamare il metodo init per MySubClass, questo codice si arresta in modo anomalo con un BAD_ACCESS. Non sono stato in grado di trovare riferimenti nulli o qualsiasi altra cosa che spiegasse questo incidente.

+0

Non riesco a riprodurre BAD_ACCESS. Presumo che tu stia usando una chiamata asincrona, forse il problema è lì intorno. – rintaro

+0

@rintaro - Sono finalmente riuscito a isolare il problema. Risulta essere non correlato all'asincronia e invece si riferisce al mix di un inizializzatore sovrascritto con un inizializzatore richiesto (pubblicherò una descrizione completa sotto). Poiché è stata la tua risposta che almeno mi ha indicato nella giusta direzione mi piacerebbe darti il ​​giusto credito per la risposta qui, ma sembra che tu abbia cancellato la tua risposta. Sentiti libero di ripubblicarti se vuoi che ti dia credito per il tuo aiuto. –

risposta

5

Con un sacco di aiuto dalla risposta originariamente pubblicata da @rintaro, sono stato in grado di risolvere questo problema, anche se c'è ancora una stranezza che posterò con una domanda a parte.

Come si è visto, @rintaro era assolutamente corretto nella necessità di inizializzare l'istanza del tipo generico utilizzando la seguente sintassi:

let result = (T.self as T.Type)(myRecord) 

Questo funziona fino a quando la classe base MyBaseClass dichiara questo initializer con un tag required:

public class MyBaseClass { 
    public required init(_ record:MyRecordType) { 
     ... 
    } 
} 

e la sottoclasse MySubClass implementa l'inizializzatore matching:

public class MySubClass : MyBaseClass { 
    public required init (_ record:MyRecordType) { 
     ... 
     super.init(record) 
    } 
} 

In caso di errore, tuttavia, è quando ho effettivamente una gerarchia di classi a 3 livelli e la gerarchia di inizializzazione attraversa un mix override. Per immaginare questo, prendere in considerazione una serie di classe che rappresenta i nodi di una struttura ad albero:

public class Node { 
    public init(_ record:MyRecordType) { 
     ... 
    } 
} 

public class RootNode : Node { 
    override public init(_ record:MyRecordType) { 
     ... 
     super.init(record) 
    } 
    public class func <T:RootNode>retrieveAll(success:(T) ->()) { 
     // Retrieve all instances of root node subclass T, and invoke success callback with new T instance 
    } 
} 

public class ChildNode : Node { 
    public init(_ record:MyRecordType, parentNode:Node) { 
     ... 
     super.init(record) 
    } 
    public class func <T:ChildNode>retrieveChildren(parent:Node, success:(T) ->()) { 
     // Retrieve all child T instances of parent node, and invoke success callback with new T instance 
    { 
} 

Il problema si verifica per l'attuazione del metodo della classe RootNoderetrieveAll. Per poter funzionare come descritto da @rintaro, ho bisogno che lo init in RootNode sia contrassegnato con la parola chiave required. Ma poiché sovrascrive anche l'inizializzatore da Node, deve anche avere la parola chiave override. Quindi cerco di usare entrambe le parole chiave nella dichiarazione:

override required public init(_ record:MyRecordType) { 
    ... 
} 

Il compilatore Swift accetta questo, ma quando lo uso per inizializzare un'istanza dall'interno del metodo retrieveAll, si blocca con un BAD_ACCESS.

sono stato in grado di risolvere questo problema modificando leggermente la firma del metodo del NodeClass in modo che la sua RootNode sottoclasse non ha bisogno di ignorare:

public class Node { 
    public init(record:MyRecordType) { 
     ... 
    } 
} 
public class RootNode { 
    public required init(_ record:MyRecordType) { 
     ... 
     super.init(record:record) 
    } 
} 

Facendo questa soluzione, i miei sottoclassi di RootNode può implementare correttamente l'inizializzatore richiesto e il metodo retrieveAll in RootNode può istanziare correttamente le istanze di tali sottoclassi.

+1

Una volta aggiunto l'inizializzatore richiesto alla classe base e alle sottoclassi, puoi semplicemente usare 'T()'. –

+0

@seanwoodward - Sfortunatamente questo non sembra essere vero. Quando chiamo l'inizializzatore usando T (myRecord) fallisce, quando lo chiamo nel modo in cui rintaro suggerisce che funzioni. L'errore con T (myRecord) sembra verificarsi a causa di un'eccezione BAD_ACCESS quando si ottiene la proprietà "record" dell'istanza, che avrebbe dovuto essere impostata sul valore di "myRecord" passato. –

+1

Grazie per averlo notato. La chiamata all'inizializzatore generico 'T()' ha funzionato per me in semplici test. Spero che i problemi possano essere risolti nelle versioni future del compilatore. –

Problemi correlati