Swift - метод класса, который должен быть переопределен подклассом

91

Есть ли в Swift стандартный способ сделать "чистую виртуальную функцию", т.е. тот, который должен быть переопределен каждым подклассом, и который, если это не так, вызывает ошибку времени компиляции?

JuJoDi
источник
Вы можете реализовать это в суперклассе и сделать утверждение. Я видел, как это используется в Obj-C, Java и Python.
Дэвид Скрундз
8
@NSArray Это вызывает ошибку времени выполнения, а не времени компиляции
JuJoDi
Этот ответ вам тоже поможет. введите описание ссылки здесь
Чамат Дживан
Чистая виртуальная функция реализуется с помощью protocols (по сравнению с interfaces в Java). Если вам нужно использовать их как абстрактные методы, посмотрите этот вопрос / ответ: stackoverflow.com/a/39038828/2435872
jboi

Ответы:

153

У вас есть два варианта:

1. Используйте протокол

Определите суперкласс как протокол вместо класса

Pro : во время компиляции проверяется, реализует ли каждый "подкласс" (не фактический подкласс) требуемый метод (ы)

Против : "суперкласс" (протокол) не может реализовывать методы или свойства.

2. Утвердить в супер версии метода

Пример:

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

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

Pro : может реализовывать методы и свойства в суперклассе.

Con : нет проверки времени компиляции

Дрюаг
источник
3
@jewirth, у вас все равно не будет проверки времени компиляции подклассов
drewag
6
Протокол не может реализовывать методы, но вместо этого вы можете предоставить их через методы расширения.
Дэвид Моулс
2
Начиная с Swift 2.0 теперь есть и расширения протокола :) Ссылка на Apple .
Ephemera
4
Хотя fatalErrorне обеспечивает проверку во время компиляции, приятно, что компилятор, по крайней мере, достаточно умен, чтобы не требовать от вас предоставления возвращаемого значения для метода при вызове пути выполнения fatalError.
bugloaf
3
Случай 2: помните, что если вы вызываете super.someFunc()из переопределенного метода, вы получаете ошибку, несмотря на то, что вы ее переопределили. Вы знаете, что не должны называть это, но кому-то еще не обязательно это знать, и он просто должен следовать стандартной практике.
Jakub Truhlář
50

Следующее позволяет наследовать от класса, а также проверять время компиляции протокола :)

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
источник
2
хорошо, typealias на помощь :)
Крис Аллинсон
Есть ли способ запретить пользователям этого API наследовать свои классы clild из ViewControllerClass, а не из ViewController? Это отличное решение для меня, потому что через несколько лет я буду извлекать из своего псевдонима типа и к тому времени уже забуду о том, какие функции нужно переопределить.
Дэвид Ректор
@David Rector, можете ли вы сделать свой класс приватным, а ваш типалиас публичным? Извините, сообщение с моего телефона, не могу проверить себя.
ScottyBlades 03
1
Прекрасное решение, спасибо за это. Как подчеркнуто @DavidRector, было бы здорово, если бы существовало решение сделать так, чтобы только typealias были общедоступными, но, к сожалению, это не представляется возможным.
CyberDandy
35

Нет никакой поддержки абстрактных классов / виртуальных функций, но вы, вероятно, можете использовать протокол в большинстве случаев:

protocol SomeProtocol {
    func someMethod()
}

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

Если SomeClass не реализует someMethod, вы получите эту ошибку времени компиляции:

error: type 'SomeClass' does not conform to protocol 'SomeProtocol'
Коннор
источник
30
Обратите внимание, что это работает только для самого верхнего класса, реализующего протокол. Любые подклассы могут беспечно игнорировать требования протокола.
меммонс
2
Также не поддерживается использование дженериков в протоколах = (
Dielson Sales
14

Другой обходной путь, если у вас не слишком много «виртуальных» методов, состоит в том, чтобы подкласс передавал «реализации» в конструктор базового класса как объекты функции:

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)
    }
}
Дэвид Молес
источник
1
не очень полезно, если у вас есть еще несколько виртуальных методов
Bushra Shahid
@ xs2bush Если у вас больше виртуальных методов, чем нет, вам, вероятно, лучше объявить их в протоколе и предоставить «невиртуальные» методы с помощью методов расширения.
Дэвид Моулс
1
это именно то, чем я закончил
Бушра Шахид
1

Вы можете использовать протокол против утверждения , как предложено в ответ здесь путем drewag. Однако пример протокола отсутствует. Я здесь прикрываюсь,

Протокол

protocol SomeProtocol {
    func someMethod()
}

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

Теперь все подклассы необходимы для реализации протокола, который проверяется во время компиляции. Если SomeClass не реализует someMethod, вы получите эту ошибку времени компиляции:

ошибка: тип SomeClass не соответствует протоколу SomeProtocol

Примечание: это работает только для самого верхнего класса, реализующего протокол. Любые подклассы могут беспечно игнорировать требования протокола. - так прокомментировал поmemmons

Утверждение

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

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

Однако утверждение будет работать только во время выполнения.

Сазад Хисейн Хан
источник
-2

Поскольку я новичок в разработке iOS, я не совсем уверен, когда это было реализовано, но один из способов получить лучшее из обоих миров - реализовать расширение для протокола:

protocol ThingsToDo {
    func doThingOne()
}

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

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

Расширение - это то, что позволяет вам иметь значение по умолчанию для функции, в то время как функция в обычном протоколе по-прежнему выдает ошибку времени компиляции, если не определена.

Иордания
источник
1
абстрактные функции противоположны реализациям по умолчанию
Hogdotmac 01