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

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


person JuJoDi    schedule 08.06.2014    source источник
comment
Вы можете реализовать это в суперклассе и сделать утверждение. Я видел, как это использовалось в Obj-C, Java и Python.   -  person David Skrundz    schedule 09.06.2014
comment
@NSArray Это вызывает ошибку времени выполнения, а не времени компиляции   -  person JuJoDi    schedule 09.06.2014
comment
Этот ответ вам тоже поможет. введите здесь описание ссылки   -  person Chamath Jeevan    schedule 04.04.2016
comment
Чистая виртуальная функция реализована protocols (по сравнению с interfaces в Java). Если вам нужно использовать их как абстрактные методы, посмотрите этот вопрос / ответ: stackoverflow.com/a/39038828/2435872   -  person jboi    schedule 19.08.2016


Ответы (8)


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

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

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

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

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

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

Пример:

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

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

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

Недостаток: отсутствие проверки времени компиляции

person drewag    schedule 08.06.2014
comment
Мне нравится идея совмещения обоих вариантов. Итак, у вас есть суперкласс, который соответствует протоколу, а каждый подкласс должен иметь свою собственную реализацию. - person Jens Wirth; 09.06.2014
comment
@jewirth вы все равно не получите проверку времени компиляции подклассов - person drewag; 09.06.2014
comment
Протокол не может реализовывать методы, но вместо этого вы можете предоставить их с помощью методов расширения. - person David Moles; 11.03.2015
comment
Начиная с Swift 2.0, теперь есть и расширения протокола :) Справочник Apple. - person Ephemera; 13.09.2015
comment
Проблема использования протоколов для имитации виртуальных и / или абстрактных функций заключается в том, что вам нужно будет как унаследовать от класса шаблона, так и принять относительный протокол, конечно, будьте осторожны, чтобы не принимать протокол в классе шаблона, иначе он принял бы его для подкласс. В целом механизм довольно сложный и в итоге бесполезный. Надеюсь, теперь Swift открыт для разработчиков, одним из первых действий будет реализация полноценных виртуальных функций! - person Fabrizio Bartolomucci; 04.12.2015
comment
Хотя fatalError не обеспечивает проверку во время компиляции, приятно, что компилятор, по крайней мере, достаточно умен, чтобы не требовать от вас предоставления возвращаемого значения для метода, когда путь выполнения вызывает fatalError. - person bugloaf; 07.03.2017
comment
Случай 2: Помните, что если вы вызываете super.someFunc() из замещенного метода, вы получите ошибку, несмотря на то, что вы ее переопределили. Вы знаете, что вы не должны называть это, но кому-то еще не обязательно это знать и просто следовать стандартной практике. - person Jakub Truhlář; 13.07.2018
comment
Есть еще и третий вариант. Вы можете передать функцию как аргумент конструктора. Пожалуйста, проверьте мой ответ, чтобы узнать подробности. - person Pikacz; 29.12.2020

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

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() { ... }

}
person JMiguel    schedule 13.07.2018
comment
приятно, typealias спешат на помощь :) - person Chris Allinson; 26.08.2018
comment
Есть ли способ запретить пользователям этого API наследовать свои классы clild из ViewControllerClass, а не из ViewController? Это отличное решение для меня, потому что через несколько лет я буду извлекать из своего псевдонима типа и к тому времени уже забуду о том, какие функции нужно переопределить. - person David Rector; 04.01.2019
comment
@David Rector, можете ли вы сделать свой класс частным, а ваш типалиас - общедоступным? Извините, сообщение с моего телефона, не могу проверить себя. - person ScottyBlades; 03.02.2019
comment
Прекрасное решение, спасибо за это. Как подчеркнуто @DavidRector, было бы здорово, если бы существовало решение, позволяющее сделать так, чтобы только typealias были общедоступными, но, к сожалению, это невозможно. - person CyberDandy; 14.05.2019
comment
красивое, самое элегантное решение, которое вызывает ошибку во время компиляции! - person Bruce; 15.12.2020

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

protocol SomeProtocol {
    func someMethod()
}

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

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

error: type 'SomeClass' does not conform to protocol 'SomeProtocol'
person Connor    schedule 08.06.2014
comment
Обратите внимание, что это работает только для самого верхнего класса, реализующего протокол. Любые подклассы могут беспечно игнорировать требования протокола. - person memmons; 15.10.2015
comment
Также не поддерживается использование дженериков в протоколах = ( - person Dielson Sales; 08.09.2016

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

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)
    }
}
person David Moles    schedule 30.06.2014
comment
не так полезно, если у вас есть еще несколько виртуальных методов - person Bushra Shahid; 11.03.2015
comment
@ xs2bush Если у вас больше виртуальных методов, чем нет, вам, вероятно, лучше объявить их в протоколе и предоставить «невиртуальные» методы с помощью методов расширения. - person David Moles; 11.03.2015
comment
это именно то, чем я закончил - person Bushra Shahid; 24.03.2015

Вы можете использовать протокол и утверждение, как это предлагается в здесь ответе 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() {
    }
}

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

person Sazzad Hissain Khan    schedule 10.01.2020

Это то, что я обычно делаю, чтобы вызвать ошибку времени компиляции:

class SuperClass {}

protocol SuperClassProtocol {
    func someFunc()
}

typealias SuperClassType = SuperClass & SuperClassProtocol


class Subclass: SuperClassType {
    func someFunc() {
        // ...
    }
}
person Amirca    schedule 20.01.2021

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

Например

open class SuperClass {
    private let abstractFunction: () -> Void

    public init(abstractFunction: @escaping () -> Void) {
        self.abstractFunction = abstractFunction
    }

    public func foo() {
        // ...
        abstractFunction()
    }
}

public class SubClass: SuperClass {
    public init() {
        super.init(
            abstractFunction: {
                print("my implementation")
            } 
        )
    }
}

Вы можете расширить его, передав self в качестве параметра:

open class SuperClass {
    private let abstractFunction: (SuperClass) -> Void

    public init(abstractFunction: @escaping (SuperClass) -> Void) {
        self.abstractFunction = abstractFunction
    }

    public func foo() {
        // ...
        abstractFunction(self)
    }
}

public class SubClass: SuperClass {
    public init() {
        super.init(
            abstractFunction: {
                (_self: SuperClass) in
                let _self: SubClass = _self as! SubClass
                print("my implementation")
            }
        )
    }
}

Pro:

  • Проверка во время компиляции, реализует ли каждый подкласс требуемый метод (ы)
  • Может реализовывать методы и свойства в суперклассе
  • Обратите внимание, что вы не можете передать self в функцию, чтобы не получить утечку памяти.

Против:

  • Это не самый красивый код
  • Вы не можете использовать его для классов с required init
person Pikacz    schedule 29.12.2020

Поскольку я новичок в разработке 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
    }
}

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

person Jordan    schedule 29.06.2018
comment
абстрактные функции противоположны реализациям по умолчанию - person Hogdotmac; 01.07.2019