Swift - metoda klasy, która musi zostać przesłonięta przez podklasę

91

Czy istnieje standardowy sposób tworzenia „czystej funkcji wirtualnej” w języku Swift, tj. taki, który musi być nadpisany przez każdą podklasę, a jeśli tak nie jest, powoduje błąd czasu kompilacji?

JuJoDi
źródło
Możesz to zaimplementować w superklasie i zrobić asercję. Widziałem to używane w Obj-C, Javie i Pythonie.
David Skrundz
8
@NSArray To powoduje błąd w czasie wykonywania, a nie kompilacji
JuJoDi
Ta odpowiedź też ci pomoże. wprowadź opis linku tutaj
Chamath Jeevan
Czysta funkcja wirtualna jest implementowana przez protocols (w porównaniu do interfaces w Javie) .Jeśli potrzebujesz ich używać jak metod abstrakcyjnych, spójrz na to pytanie / odpowiedź: stackoverflow.com/a/39038828/2435872
jboi

Odpowiedzi:

153

Masz dwie możliwości:

1. Użyj protokołu

Zdefiniuj nadklasę jako protokół zamiast klasy

Pro : Sprawdzanie czasu kompilacji, czy każda „podklasa” (a nie rzeczywista podklasa) implementuje wymagane metody

Wada : „Nadklasa” (protokół) nie może implementować metod ani właściwości

2. Assert w super wersji metody

Przykład:

class SuperClass {
    func someFunc() {
        fatalError("Must Override")
    }
}

class Subclass : SuperClass {
    override func someFunc() {
    }
}

Pro : Potrafi implementować metody i właściwości w nadklasie

Wada : brak kontroli czasu kompilacji

drewag
źródło
3
@jewirth nadal nie dostaniesz sprawdzenia czasu kompilacji podklas
drewag
6
Protokół nie może implementować metod, ale zamiast tego można je udostępnić za pomocą metod rozszerzających.
David Moles
2
Od wersji Swift 2.0 są teraz również rozszerzenia protokołów :) Dokumentacja Apple .
Ephemera
4
Chociaż fatalErrornie zapewnia sprawdzania czasu kompilacji, fajnie jest, że kompilator jest przynajmniej wystarczająco inteligentny, aby nie wymagać od ciebie dostarczania wartości zwracanej dla metody, gdy wywoływana jest ścieżka wykonania fatalError.
bugloaf
3
Przypadek 2: Pamiętaj, że jeśli wywołasz super.someFunc()metodę z nadpisaniem, otrzymasz błąd pomimo tego, że go przesłoniłeś. Wiesz, że nie powinieneś tego nazywać, ale ktoś inny nie musi o tym wiedzieć i po prostu postępować zgodnie ze standardową praktyką.
Jakub Truhlář
50

Poniższe pozwala na dziedziczenie z klasy, a także na sprawdzenie czasu kompilacji protokołu :)

protocol ViewControllerProtocol {
    func setupViews()
    func setupConstraints()
}

typealias ViewController = ViewControllerClass & ViewControllerProtocol

class ViewControllerClass : UIViewController {

    override func viewDidLoad() {
        self.setup()
    }

    func setup() {
        guard let controller = self as? ViewController else {
            return
        }

        controller.setupViews()
        controller.setupConstraints()
    }

    //.... and implement methods related to UIViewController at will

}

class SubClass : ViewController {

    //-- in case these aren't here... an error will be presented
    func setupViews() { ... }
    func setupConstraints() { ... }

}
JMiguel
źródło
2
miło, typalias na ratunek :)
Chris Allinson
Jakikolwiek sposób, aby uniemożliwić użytkownikom tego interfejsu API wyprowadzanie ich klas clild z ViewControllerClass zamiast z ViewController? Jest to dla mnie świetne rozwiązanie, ponieważ za kilka lat zacznę pochodzić od mojego aliasu typu i zapomnę o tym, jakie funkcje należy do tego czasu zastąpić.
David Rector
@David Rector, czy możesz ustawić swoją klasę jako prywatną, a alias typu publicznego? Przepraszam, wysyłam wiadomość z mojego telefonu, nie mogę się sprawdzić.
ScottyBlades
1
Idealne rozwiązanie, dziękuję za to. Jak podkreślił @DavidRector, byłoby wspaniale, gdyby istniało rozwiązanie, które sprawi, że tylko alias typu był publiczny, ale niestety nie wydaje się to możliwe.
CyberDandy
35

Nie ma wsparcia dla abstrakcyjnych klas / funkcji wirtualnych, ale prawdopodobnie w większości przypadków możesz użyć protokołu:

protocol SomeProtocol {
    func someMethod()
}

class SomeClass: SomeProtocol {
    func someMethod() {}
}

Jeśli SomeClass nie zaimplementuje someMethod, zostanie wyświetlony następujący błąd kompilacji:

error: type 'SomeClass' does not conform to protocol 'SomeProtocol'
Connor
źródło
30
Zauważ, że działa to tylko dla najwyższej klasy, która implementuje protokół. Każda podklasa może beztrosko zignorować wymagania protokołu.
memmons
2
Ponadto używanie
typów
14

Innym obejściem, jeśli nie masz zbyt wielu "wirtualnych" metod, jest przekazanie podklasy "implementacji" do konstruktora klasy bazowej jako obiektów funkcji:

class MyVirtual {

    // 'Implementation' provided by subclass
    let fooImpl: (() -> String)

    // Delegates to 'implementation' provided by subclass
    func foo() -> String {
        return fooImpl()
    }

    init(fooImpl: (() -> String)) {
        self.fooImpl = fooImpl
    }
}

class MyImpl: MyVirtual {

    // 'Implementation' for super.foo()
    func myFoo() -> String {
        return "I am foo"
    }

    init() {
        // pass the 'implementation' to the superclass
        super.init(myFoo)
    }
}
David Moles
źródło
1
nie tak przydatne, jeśli masz kilka bardziej wirtualnych metod
Bushra Shahid
@ xs2bush Jeśli więcej twoich metod jest wirtualnych niż nie, prawdopodobnie lepiej będzie zadeklarować je w protokole i udostępnić "niewirtualne" metodami rozszerzającymi.
David Moles
1
to jest dokładnie to, co ostatecznie
zrobiłem
1

Można użyć protokołu vs twierdzeniu, jak zasugerowano w odpowiedzi tutaj przez drewag. Brakuje jednak np. Protokołu. Kryję się tutaj,

Protokół

protocol SomeProtocol {
    func someMethod()
}

class SomeClass: SomeProtocol {
    func someMethod() {}
}

Teraz wszystkie podklasy są wymagane do implementacji protokołu sprawdzanego w czasie kompilacji. Jeśli SomeClass nie zaimplementuje someMethod, zostanie wyświetlony następujący błąd kompilacji:

błąd: typ „SomeClass” nie jest zgodny z protokołem „SomeProtocol”

Uwaga: działa to tylko dla najwyższej klasy, która implementuje protokół. Każda podklasa może beztrosko zignorować wymagania protokołu. - tak skomentował przezmemmons

Twierdzenie

class SuperClass {
    func someFunc() {
        fatalError("Must Override")
    }
}

class Subclass : SuperClass {
    override func someFunc() {
    }
}

Jednak asercja będzie działać tylko w czasie wykonywania.

Sazzad Hissain Khan
źródło
-2

Będąc nowym w rozwoju iOS, nie jestem do końca pewien, kiedy to zostało zaimplementowane, ale jednym ze sposobów uzyskania najlepszego z obu światów jest zaimplementowanie rozszerzenia dla protokołu:

protocol ThingsToDo {
    func doThingOne()
}

extension ThingsToDo {
    func doThingTwo() { /* Define code here */}
}

class Person: ThingsToDo {
    func doThingOne() {
        // Already defined in extension
        doThingTwo()
        // Rest of code
    }
}

To rozszerzenie pozwala ci mieć domyślną wartość dla funkcji, podczas gdy funkcja w zwykłym protokole nadal dostarcza błąd czasu kompilacji, jeśli nie jest zdefiniowany

Jordania
źródło
1
abstrakcyjne funkcje są przeciwieństwem domyślnych implementacji
Hogdotmac