2016-07-11 45 views
6

Se io dichiaroSwift: "initializer failable 'init()' non può ignorare un inizializzatore non failable" vs. parametri di default

public class A: NSObject { 
    public class X { } 
    public init?(x: X? = nil) { } 
} 

tutto va bene. Quando lo si utilizza come let a = A(), l'inizializzatore viene chiamato come previsto.

Ora, mi piacerebbe avere la classe annidata X privato, e la parametrizzato init pure (deve essere, ovviamente). Ma un semplice init?() dovrebbe rimanere disponibile pubblicamente com'era prima. Così scrivo

public class B: NSObject { 
    private class X { } 
    private init?(x: X?) { } 
    public convenience override init?() { self.init(x: nil) } 
} 

Ma questo dà un errore con la init?() inizializzatore: initializer 'init()' failable non può escludere un inizializzatore non failable con l'inizializzatore override essere il public init() in NSObject.

Come posso effettivamente dichiarare un inizializzatore A.init?() senza il conflitto ma non B.init?()?

Domanda bonus: Perché non è possibile sovrascrivere un inizializzatore non disponibile con uno disponibile? L'opposto è legale: riesco a sovrascrivere un inizializzatore disponibile con un non-disponibile, che richiede l'utilizzo di un numero forzato super.init()! e introduce quindi il rischio di un errore di runtime. Per me, lasciare che la sottoclasse abbia l'inizializzatore utilizzabile è più sensato dal momento che un'estensione della funzionalità introduce maggiori possibilità di errore. Ma forse mi manca qualcosa qui - spiegazione molto apprezzata.

+0

"override" implica un metodo con la stessa firma nella superclasse. Comunque non c'è 'init?' In 'NSObject' – vadian

+0

@vadian: Sì, ma omettendo' override' il messaggio di errore 'la dichiarazione di override richiede una parola chiave 'override', quindi conta comunque come override. Correzione: sta inserendo 'override', dando l'altro errore. Inoltre, ho il permesso di sovrascrivere un init failable con un non-failable, ma non viceversa. – Stefan

risposta

2

Dopo un po 'di giocherellare penso di aver capito. Prendiamo in considerazione un protocollo che richiede questo inizializzatore e una classe attuazione:

protocol I { 
    init() 
} 

class A : I { 
    init() {} 
} 

Questo dà l'errore: "Initializer requisito 'init()' può essere soddisfatta solo da una required inizializzatore in non-finale di classe 'A'" . Questo ha senso, come si può sempre dichiarare una sottoclasse di A che non eredita che inizializzatore:

class B : A { 
    // init() is not inherited 
    init(n: Int) {} 
} 

così abbiamo bisogno per rendere il nostro initializer in Arequired:

class A : I { 
    required init() {} 
} 

Ora, se guardiamo a livello di interfaccia NSObject possiamo vedere che l'inizializzatore è nonrequired:

public class NSObject : NSObjectProtocol { 
    [...] 
    public init() 
    [...] 
} 

Possiamo confermare questo sottoclasse esso, l'aggiunta di un inizializzatore diverso e cercando di utilizzare quella normale:

class MyObject : NSObject { 
    init(n: Int) {} 
} 

MyObject() // Error: Missing argument for parameter 'n:' in call 

Ora arriva la cosa strana: Siamo possiamo estendere NSObject di conformarsi al protocollo I, anche anche se non lo richiede inizializzatore:

extension NSObject : I {} // No error (!) 

onestamente che questo è sia un bug o un requisito per objC interoperabilità al lavoro (EDIT: e 'un bug e già fissato nella sua ultima versione).Questo errore non dovrebbe essere possibile:

extension I { 
    static func get() -> Self { return Self() } 
} 

MyObject.get() 
// Runtime error: use of unimplemented initializer 'init()' for class '__lldb_expr_248.MyObject' 

Ora per rispondere alla tua domanda effettiva:

Nel secondo esempio di codice, il compilatore è proprio che non si può ignorare un non-failable con un inizializzatore failable.

Nel primo, non si sta sovrascrivendo l'inizializzatore (nessuna parola chiave override), ma invece si dichiara un nuovo in base al quale l'altro non può essere ereditato.

Ora che ho scritto così tanto non sono nemmeno sicuro di quale sia la prima parte della mia risposta alla domanda, ma è comunque carino trovare un bug.

vi consiglio di fare questo, invece:

public convenience override init() { self.init(x: nil)! } 

hanno anche uno sguardo al Initialization section di riferimento Swift.

+0

Grazie per la tua risposta dettagliata. Il problema di fondo è che l'inizializzatore della mia sottoclasse ** può ** fallire poiché ci sono risorse esterne coinvolte ('NSCoding' ...), quindi è (tipo di) necessario per verificare se l'inizializzatore ha avuto successo. Ma, per favore date un'occhiata alla mia addendum "PS" usando un parametro 'Void'. – Stefan

+0

Nota che il bug che descrivi sopra ('protocollo I {init()}' seguito da 'estensione NSObject: I {}') _does_ produce un errore in Swift 3.0-dev (_error: initializer requirement ''init()'' può essere soddisfatto solo da un inizializzatore 'required' nella classe non finale '' NSObject''_), quindi penso che sia stato corretto. – dfri

+0

@dfri Ohh è bello, grazie per avermi fatto sapere – Kametrixom

5

Ecco come ho risolto il problema per me:

io posso dichiarare

public convenience init?(_: Void) { self.init(x: nil) } 

e utilizzarlo come

let b = B(()) 

o anche

let b = B() 

- il che è logico dal momento che la sua firma è (un po 'diversa), quindi non ignorare qui. Usare solo un parametro Void e ometterlo nella chiamata sembra un po 'strano ... Ma il fine giustifica i mezzi, suppongo. :-)

+0

Grazie, ho trovato questo molto utile per un modello di db di Realm in cui avevo solo un paio di classi di oggetti gestite che non dovrebbero mai essere inizializzate con contenuti vuoti, solo con un elenco completo di valori. Con questo modulo potrei ancora chiamare il costruttore di Realm Object init (value: Any) poiché potrei usare l'etichetta del valore per distinguere l'inizializzatore della superclasse dal mio failable –