【问题标题】:Swift - class method which must be overridden by subclassSwift - 必须被子类覆盖的类方法
【发布时间】:2014-07-29 11:12:03
【问题描述】:

在 Swift 中是否有一种标准的方法来制作“纯虚函数”,即。 必须被每个子类覆盖,如果不是,会导致编译时错误?

【问题讨论】:

  • 你可以在超类中实现它并做出断言。我已经在 Obj-C、Java 和 Python 中看到过这个。
  • @NSArray 这会导致运行时错误,而不是编译时错误
  • 这个答案也会对你有所帮助。 enter link description here
  • 纯虚函数由protocols 实现(与Java 中的interfaces 相比)如果您需要像抽象方法一样使用它们,请查看此问题/答案:stackoverflow.com/a/39038828/2435872

标签: swift abstract swift-protocols


【解决方案1】:

不支持抽象类/虚函数,但在大多数情况下您可以使用协议:

protocol SomeProtocol {
    func someMethod()
}

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

如果 SomeClass 没有实现 someMethod,你会得到这个编译时错误:

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

【讨论】:

  • 请注意,这只适用于实现协议的最顶层类。任何子类都可以愉快地忽略协议要求。
  • 另外,不支持在协议上使用泛型 =(
【解决方案2】:

你有两个选择:

1。使用协议

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

专业版:编译时检查每个“子类”(不是实际的子类)是否实现了所需的方法

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

2。在方法的超级版本中断言

例子:

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

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

专业版:可以实现超类中的方法和属性

缺点:没有编译时检查

【讨论】:

  • @jewirth 您仍然无法对子类进行编译时检查
  • 协议无法实现方法,但可以通过扩展方法提供。
  • 从 Swift 2.0 开始,现在也有协议扩展 :) Apple Reference.
  • 虽然 fatalError 不提供编译时检查,但编译器至少足够聪明,在执行路径调用 @ 时不需要您为方法提供返回值,这很好。 987654324@.
  • 案例 2: 请注意,如果您从被覆盖的方法中调用 super.someFunc(),尽管您已经覆盖了它,但您仍会收到错误消息。您知道您不应该调用它,但其他人不必知道这一点,只需遵循标准做法即可。
【解决方案3】:

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

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

【讨论】:

  • 如果你有更多的虚拟方法,那就没那么有用了
  • @xs2bush 如果您的方法中有更多是虚拟的,那么您最好在协议中声明它们,并通过扩展方法提供“非虚拟”方法。
  • 这正是我最终做的事情
【解决方案4】:

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

扩展是允许您拥有函数的默认值,而常规协议中的函数如果未定义仍会提供编译时错误

【讨论】:

  • 抽象函数与默认实现相反
【解决方案5】:

以下允许从类继承并检查协议的编译时间:)

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

}

【讨论】:

  • 很好,救援的 typealias :)
  • 有什么方法可以阻止这个 API 的用户从 ViewControllerClass 而不是从 ViewController 派生他们的 clild 类?这对我来说是一个很好的解决方案,因为几年后我将从我的类型别名派生并且会忘记到那时需要覆盖哪些函数。
  • @David Rector,您能否将您的课程设为私有而将您的 typealias 设为公开?抱歉,我的手机发来的消息,无法检查自己。
  • 完美的解决方案,谢谢。正如@DavidRector 所强调的那样,如果有一个解决方案也能做到这一点,那就太好了,所以只有 typealias 是公开的,但不幸的是,这似乎不可能。
  • 不错,最优雅的解决方案,在编译时抛出错误!
【解决方案6】:

您可以按照drewag 的回答here 中的建议使用协议与断言。 但是,缺少该协议的示例。我在这里报道,

协议

protocol SomeProtocol {
    func someMethod()
}

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

现在每个子类都需要实现在编译时检查的协议。如果 SomeClass 没有实现 someMethod,你会得到这个编译时错误:

错误:类型“SomeClass”不符合协议“SomeProtocol”

注意:这仅适用于实现协议的最顶层类。任何子类都可以愉快地忽略协议要求。 – 作为commentedmemmons

断言

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

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

但是,断言只能在运行时起作用。

【讨论】:

    【解决方案7】:

    您可以通过将函数传递给初始化程序来实现它。

    例如

    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 的类

    【讨论】:

      【解决方案8】:

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

      class SuperClass {}
      
      protocol SuperClassProtocol {
          func someFunc()
      }
      
      typealias SuperClassType = SuperClass & SuperClassProtocol
      
      
      class Subclass: SuperClassType {
          func someFunc() {
              // ...
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-12-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-05-11
        • 2019-01-30
        相关资源
        最近更新 更多