2016-07-11 10 views
6

Jeśli oświadczamSwift: "failable inicjator 'init()' nie może przesłonić non-failable inicjator" vs. domyślnymi parametrami

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

wszystko jest w porządku. Gdy używa się go jak let a = A(), inicjator jest wywoływany zgodnie z oczekiwaniami.

Teraz chciałbym mieć klasę zagnieżdżoną X prywatną, a także sparametryzować init (oczywiście musi to być). Ale prosty init?() powinien pozostać publicznie dostępny, tak jak był wcześniej. Więc piszę

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

Ale to daje błąd z init?() inicjatora: failable inicjująca 'init()' nie może przesłonić non-failable inicjator z przesłonięte initializer będąc public init() w NSObject.

W jaki sposób mogę skutecznie zadeklarować inicjator A.init?() bez konfliktu, ale nie pod numerem B.init?()?

Pytanie dodatkowe: Dlaczego nie można zastąpić inicjalizatora, który nie może ulec awarii, błędnym? Odwrotna sytuacja jest prawidłowa: mogę unieważnić inicjator failable, który nie jest uszkodzony, co wymaga użycia wymuszonego super.init()! i tym samym wprowadza ryzyko błędu runtime. Dla mnie, pozwolenie, aby podklasa miała wadliwy inicjator, wydaje się rozsądniejsze, ponieważ rozszerzenie funkcjonalności wprowadza większe ryzyko niepowodzenia. Ale może czegoś tu brakuje - wyjaśnienie bardzo doceniane.

+0

'override' oznacza metodę z tą samą sygnaturą w superklasie. Jednak w 'NSObject' nie ma' init? ' – vadian

+0

@vadian: Tak, ale pomijanie' override' powoduje wyświetlenie komunikatu o błędzie 'nadpisująca deklaracja wymaga" override "słowa kluczowego', więc i tak liczy się jako nadpisująca. Napraw - wstawia 'override', dając inny błąd. Co więcej, jestem upoważniony do przesłonięcia niezawartego init z nie-failable, ale nie na odwrót. – Stefan

Odpowiedz

2

Po odrobinie manipulowania, myślę, że rozumiem. Rozważmy protokół wymagając tego inicjatora i klasę wykonawczą go:

protocol I { 
    init() 
} 

class A : I { 
    init() {} 
} 

To daje błąd: „Initializer wymaganie«init()»mogą być spełnione tylko przez required inicjatora w nieostatecznej klasy«A»” . Ma to sens, ponieważ zawsze można zadeklarować podklasę A że nie dziedziczy, że inicjator:

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

więc musimy uczynić nasze inicjatora w Arequired:

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

Teraz jeśli spojrzymy na styku NSObject widzimy, że inicjator jest nierequired:

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

Możemy to potwierdzić przez instacji go, dodając inny inicjator i próbuje użyć normalny:

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

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

Teraz tu pojawia się dziwna rzecz: My może przedłużyć NSObject aby były zgodne z protokołem I, nawet mimo że nie wymaga to inicjator:

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

szczerze, że jest to albo błąd lub wymóg ObjC współdziałanie pracować (EDIT: jest to błąd i już rozwiązany w najnowszej wersji).Ten błąd nie powinno być możliwe:

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

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

teraz, aby odpowiedzieć na rzeczywiste pytanie:

w drugim próbki kodu, kompilator ma rację, że nie można przesłonić niebędącego failable z failable inicjatora.

W pierwszym, tak naprawdę nie nadpisujesz inicjalizatora (bez słowa kluczowego override), ale zamiast tego deklarujesz nowy, przez który drugi nie może zostać odziedziczony.

Teraz, gdy napisałem tak dużo, nie jestem nawet pewien, co jest pierwszą częścią mojej odpowiedzi na twoje pytanie, ale miło jest znaleźć błąd i tak.

Proponuję zrobić to zamiast:

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

Również spojrzeć na Initialization section SWIFT odniesienia.

+0

Dziękuję za szczegółową odpowiedź. Podstawowy problem polega na tym, że inicjator mojej podklasy ** może ** się nie powieść, ponieważ zaangażowane są zasoby zewnętrzne ('NSCoding' ...), więc jest to (rodzaj) niezbędne do sprawdzenia, czy inicjator się powiódł. Ale proszę spojrzeć na mój addendum "PS", używając parametru 'Void'. – Stefan

+0

Zauważ, że błąd, który opisałeś powyżej ('protocol I {init()}', po którym następuje 'rozszerzenie NSObject: I {}') _does_ powoduje błąd w Swift 3.0-dev (_error: initializer requirement ''init()'' może być spełniony tylko przez "wymagany" inicjator w nie-końcowej klasie "' NSObject''_), więc domyślam się, że zostało to naprawione. – dfri

+0

@dfri Ohh to miło, dziękuję za poinformowanie mnie – Kametrixom

5

To jest jak I rozwiązać problem dla mnie:

I może zadeklarować

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

i używać go jak

let b = B(()) 

lub nawet

let b = B() 

- co jest logiczne, ponieważ jego podpis jest (rodzaj) inny, więc nie nadpisuje tutaj. Tylko użycie parametru Void i pominięcie go w wywołaniu wydaje się nieco dziwne ... Ale koniec uzasadnia środki, jak przypuszczam. :-)

+0

Dzięki, znalazłem to bardzo przydatne dla modelu Realm db, gdzie miałem tylko kilka zarządzanych klas obiektów, które nigdy nie powinny być inicjowane z pustą zawartością, tylko z pełną listą wartości. Przy pomocy tego formularza mógłbym jeszcze wywołać initm obiektu Realm Object (wartość: Any), ponieważ mógłbym użyć etykiety wartości do odróżnienia inicjalizatora klasy nadklasy od mojego uszkodzonego –

Powiązane problemy