Swift - 必须被子类重写的类方法

116

在Swift中,是否有一种标准的方法来创建“纯虚函数”,即每个子类必须重写并且如果没有重写则会导致编译时错误的函数?


你可以在超类中实现它并进行断言。我曾经在 Obj-C、Java 和 Python 中看到过这种用法。 - David Skrundz
9
@NSArray 这会导致运行时错误,而不是编译时错误。 - JuJoDi
这个答案也会对你有所帮助。进入链接描述 - Chamath Jeevan
纯虚函数由protocol实现(与Java中的interface相比)。如果您需要像抽象方法一样使用它们,请查看此问题/答案:http://stackoverflow.com/a/39038828/2435872 - jboi
8个回答

179

你有两个选择:

1. 使用协议

将超类定义为协议而不是类

优点: 在编译时检查每个“子类”(实际上不是子类)是否实现了所需的方法

缺点: “超类”(协议)无法实现方法或属性

2. 在方法的超级版本中进行断言

例如:

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

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

优点: 可以在超类中实现方法和属性

缺点: 没有编译时检查


3
@jewirth,你仍然无法在子类上获得编译时检查。 - drewag
6
协议无法实现方法,但您可以使用扩展方法来提供它们。 - David Moles
2
从Swift 2.0开始,现在也有协议扩展了 :) 苹果参考 - Ephemera
4
虽然 fatalError 不提供编译时检查,但很好的是编译器至少足够聪明,在执行路径调用 fatalError 时不需要你提供方法的返回值。 - bugloaf
4
请注意,如果你在覆盖的方法中调用 super.someFunc(),尽管你已经覆盖了它,你仍然会得到错误。你知道你不应该调用它,但其他人可能不知道并且只是遵循标准惯例。 - Jakub Truhlář
显示剩余4条评论

73
以下内容允许从一个类继承并且具有协议的编译时检查 :)
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() { ... }

}

4
好的,typealias来帮忙了 :) - Chris Allinson
有没有办法阻止使用此API的用户从ViewControllerClass而不是从ViewController派生其子类?这对我来说是一个很好的解决方案,因为几年后我将从我的类型别名派生,并且到那时我将忘记哪些函数需要被覆盖。 - David Rector
1
@David Rector,你能把你的类设为私有,把你的类型别名设为公共吗?抱歉我在用手机发信息,无法自己检查。 - ScottyBlades
1
非常完美的解决方案,谢谢你。正如@DavidRector所强调的那样,如果能够找到一种方法只将typealias设为公开,那就太好了,但不幸的是似乎不可能实现。 - CyberDandy
1
最优雅的解决方案,在编译时会抛出错误! - Bruce
显示剩余2条评论

35

没有对抽象类/虚拟函数的支持,但是您可能可以在大多数情况下使用协议:

protocol SomeProtocol {
    func someMethod()
}

class SomeClass: SomeProtocol {
    func someMethod() {}
}
如果SomeClass没有实现someMethod方法,您将收到这个编译时错误:

error: type 'SomeClass' does not conform to protocol 'SomeProtocol'

32
注意,这仅适用于实现该协议的最顶层类。任何子类都可以轻易地忽略协议要求。 - memmons
3
抱歉,使用泛型在协议中不被支持。 - Dielson Sales

15

如果你没有太多的“虚拟”方法,另一个解决方法是让子类将“实现”作为函数对象传递到基类构造函数中:

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 - David Moles
1
这正是我最终所做的。 - Bushra Shahid

3

这是我通常做的,会导致编译时错误:

class SuperClass {}

protocol SuperClassProtocol {
    func someFunc()
}

typealias SuperClassType = SuperClass & SuperClassProtocol


class Subclass: SuperClassType {
    func someFunc() {
        // ...
    }
}

2
您可以像这里的答案中drewag建议的那样使用协议和断言。 但是,协议的示例缺失。我在这里进行介绍,

协议

protocol SomeProtocol {
    func someMethod()
}

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

现在每个子类都必须实现在编译时检查的协议。如果SomeClass没有实现someMethod,你将会得到以下编译时错误:

error: type 'SomeClass' does not conform to protocol 'SomeProtocol'

注意:这只适用于实现协议的最上层类。任何子类都可以轻松地忽略协议要求。- 由memmons评论。

断言

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

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

然而,断言只在运行时起作用。

0

你可以通过将函数传递到初始化器中来实现它。

例如

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")
            }
        )
    }
}

优点:

  • 编译时检查每个子类是否实现了所需的方法
  • 可以在超类中实现方法和属性
  • 请注意,您不能将self传递给函数,因此不会出现内存泄漏。

缺点:

  • 代码不够美观
  • 无法用于具有required init的类

-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
    }
}

扩展就是允许你在函数中设置默认值,而当该函数在常规协议中未定义时,仍会提供一个编译时错误。


2
抽象函数是默认实现的相反。 - Hogdotmac

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接