Swift 语言中的抽象类
Posted
技术标签:
【中文标题】Swift 语言中的抽象类【英文标题】:Abstract classes in Swift Language 【发布时间】:2014-07-29 09:50:27 【问题描述】:有没有办法在 Swift 语言中创建一个抽象类,或者这是像 Objective-C 一样的限制?我想创建一个抽象类,类似于 Java 定义的抽象类。
【问题讨论】:
你需要整个类是抽象的还是只是其中的一些方法?有关单个方法和属性,请参见此处的答案。 ***.com/a/39038828/2435872 。在 Java 中,您可以拥有没有抽象方法的抽象类。 Swift 不提供该特殊功能。 【参考方案1】:Swift 中没有抽象类(就像 Objective-C 一样)。最好的办法是使用Protocol,它类似于 Java 接口。
在 Swift 2.0 中,您可以使用协议扩展添加方法实现和计算属性实现。您唯一的限制是您不能提供成员变量或常量并且没有动态调度。
这种技术的一个例子是:
protocol Employee
var annualSalary: Int get
extension Employee
var biweeklySalary: Int
return self.annualSalary / 26
func logSalary()
print("$\(self.annualSalary) per year or $\(self.biweeklySalary) biweekly")
struct SoftwareEngineer: Employee
var annualSalary: Int
func logSalary()
print("overridden")
let sarah = SoftwareEngineer(annualSalary: 100000)
sarah.logSalary() // prints: overridden
(sarah as Employee).logSalary() // prints: $100000 per year or $3846 biweekly
请注意,这甚至为结构提供了类似“抽象类”的功能,但类也可以实现相同的协议。
还要注意,每个实现 Employee 协议的类或结构都必须再次声明 AnnualSalary 属性。
最重要的是,请注意没有动态调度。当在存储为SoftwareEngineer
的实例上调用logSalary
时,它会调用该方法的覆盖版本。当实例被转换为Employee
后在实例上调用logSalary
时,它会调用原始实现(即使实例实际上是Software Engineer
,它也不会动态分派到覆盖的版本。
欲了解更多信息,请查看有关该功能的精彩 WWDC 视频:Building Better Apps with Value Types in Swift
【讨论】:
protocol Animal var property : Int get set
。如果您不希望该属性具有 setter,也可以省略该 set
我认为this wwdc video 更相关
@MarioZannone 那个视频让我大吃一惊,让我爱上了 Swift。
如果您只是将 func logSalary()
添加到 Employee 协议声明中,那么对于对 logSalary()
的两个调用,该示例都会打印 overridden
。这是在 Swift 3.1 中。因此,您可以获得多态性的好处。在这两种情况下都会调用正确的方法。
动态调度的规则是这样...如果它也在您扩展的协议中定义,那么它是动态调度的。不需要 Objective-C 运行时。这是纯粹的 Swift 行为。【参考方案2】:
请注意,此答案针对 Swift 2.0 及更高版本
您可以通过协议和协议扩展实现相同的行为。
首先,您编写一个协议,作为所有方法的接口,这些方法必须以符合它的所有类型实现。
protocol Drivable
var speed: Float get set
然后你可以为所有符合它的类型添加默认行为
extension Drivable
func accelerate(by: Float)
speed += by
您现在可以通过实现Drivable
创建新类型。
struct Car: Drivable
var speed: Float = 0.0
init()
let c = Car()
c.accelerate(10)
所以基本上你得到:
-
编译时检查保证所有
Drivable
s 实现speed
您可以为所有符合Drivable
(accelerate
) 的类型实现默认行为
Drivable
保证不会被实例化,因为它只是一个协议
这个模型实际上更像特征,这意味着您可以遵守多种协议并采用其中任何一种的默认实现,而使用抽象超类时,您只能使用简单的类层次结构。
【讨论】:
不过,并不总是有可能扩展某些协议,例如UICollectionViewDatasource
。我想删除所有样板并将其封装在单独的协议/扩展中,然后由多个类重新使用。事实上,模板模式在这里会很完美,但是......
您不能在 ˚Car˚ 中覆盖 ˚accelerate˚。如果你这样做了,在没有任何编译器警告的情况下仍然会调用 ˚extentsion Driveable˚ 中的实现。与 Java 抽象类非常不同
@GerdCastan True,协议扩展不支持动态调度。【参考方案3】:
我认为这是最接近 Java 的 abstract
或 C# 的 abstract
:
class AbstractClass
private init()
请注意,为了使 private
修饰符起作用,您必须在单独的 Swift 文件中定义此类。
编辑:不过,此代码不允许声明抽象方法并因此强制其实现。
【讨论】:
不过,这并没有强制子类覆盖函数,同时在父类中也有该函数的基本实现。 在 C# 中,如果您在抽象基类中实现一个函数,则不必在其子类中实现它。不过,此代码不允许您声明抽象方法以强制覆盖。 可以说 ConcreteClass 是 AbstractClass 的子类。你如何实例化 ConcreteClass ? ConcreteClass 应该有一个公共构造函数。您可能需要 AbstractClass 中的受保护构造函数,除非它们在同一个文件中。据我记得,protected 访问修饰符在 Swift 中不存在。所以解决方法是在同一个文件中声明ConcreteClass。【参考方案4】:最简单的方法是使用对fatalError("Not Implemented")
的调用进入协议扩展上的抽象方法(不是变量)。
protocol MyInterface
func myMethod() -> String
extension MyInterface
func myMethod() -> String
fatalError("Not Implemented")
class MyConcreteClass: MyInterface
func myMethod() -> String
return "The output"
MyConcreteClass().myMethod()
【讨论】:
这是一个很好的答案。如果你打电话给(MyConcreteClass() as MyInterface).myMethod()
,我认为它不会起作用,但它会起作用!关键是在协议声明中包含myMethod
;否则通话会崩溃。【参考方案5】:
经过几周的努力,我终于明白了如何将Java/php抽象类翻译成Swift:
public class AbstractClass: NSObject
internal override init()
public func getFoodToEat()->String
if(self._iAmHungry())
return self._myFavoriteFood();
else
return "";
private func _myFavoriteFood()->String
return "Sandwich";
internal func _iAmHungry()->Bool
fatalError(__FUNCTION__ + "Must be overridden");
return false;
public class ConcreteClass: AbstractClass, IConcreteClass
private var _hungry: Bool = false;
public override init()
super.init();
public func starve()->Void
self._hungry = true;
public override func _iAmHungry()->Bool
return self._hungry;
public protocol IConcreteClass
func _iAmHungry()->Bool;
class ConcreteClassTest: XCTestCase
func testExample()
var concreteClass: ConcreteClass = ConcreteClass();
XCTAssertEqual("", concreteClass.getFoodToEat());
concreteClass.starve();
XCTAssertEqual("Sandwich", concreteClass.getFoodToEat());
但是我认为 Apple 没有实现抽象类,因为它通常使用委托+协议模式。例如,上面的相同模式最好像这样完成:
import UIKit
public class GoldenSpoonChild
private var delegate: IStomach!;
internal init()
internal func setup(delegate: IStomach)
self.delegate = delegate;
public func getFoodToEat()->String
if(self.delegate.iAmHungry())
return self._myFavoriteFood();
else
return "";
private func _myFavoriteFood()->String
return "Sandwich";
public class Mother: GoldenSpoonChild, IStomach
private var _hungry: Bool = false;
public override init()
super.init();
super.setup(self);
public func makeFamilyHungry()->Void
self._hungry = true;
public func iAmHungry()->Bool
return self._hungry;
protocol IStomach
func iAmHungry()->Bool;
class DelegateTest: XCTestCase
func testGetFood()
var concreteClass: Mother = Mother();
XCTAssertEqual("", concreteClass.getFoodToEat());
concreteClass.makeFamilyHungry();
XCTAssertEqual("Sandwich", concreteClass.getFoodToEat());
我需要这种模式是因为我想通用 UITableViewController 中的一些方法,例如 viewWillAppear 等。这有帮助吗?
【讨论】:
此外,如果您的两个示例都在同一个用例上,这将有所帮助。 GoldenSpoonChild 是一个有点令人困惑的名字,尤其是考虑到母亲似乎在扩展它。 @Angad 委托模式是相同的用例,但它不是翻译;这是一个不同的模式,所以它必须采取不同的视角。【参考方案6】:有一种方法可以使用协议来模拟抽象类。 这是一个例子:
protocol MyProtocol
func doIt()
class BaseClass
weak var myDelegate: MyProtocol?
init()
...
func myFunc()
...
self.myDelegate?.doIt()
...
class ChildClass: BaseClass, MyProtocol
override init()
super.init()
self.myDelegate = self
func doIt()
// Custom implementation
【讨论】:
【参考方案7】:实现抽象类的另一种方法是阻塞初始化程序。 我是这样做的:
class Element:CALayer // IT'S ABSTRACT CLASS
override init()
super.init()
if self.dynamicType === Element.self
fatalError("Element is abstract class, do not try to create instance of this class")
【讨论】:
这不提供任何保证和/或检查。在运行时炸毁是执行规则的不好方法。最好将 init 设为私有。 抽象类也应该支持抽象方法。 @Cristik 我展示了主要思想,它不是完整的解决方案。这样你可以不喜欢 80% 的答案,因为它们对你的情况不够详细 @AlexeyYarmolovich 谁说我不喜欢 80% 的答案? :) 开个玩笑,我建议您的示例可以改进,这将帮助其他读者,并通过投票来帮助您。【参考方案8】:这是一个非常古老的问题,但仍然......这是在 Swift 5.2 上编译并按预期工作的实际代码的 sn-p:
protocol Context
init() throws
func out(_ aStr: String) throws
// Other stuff
class AbstractContext: Context
required init() throws
if Self.self === AbstractContext.self
preconditionFailure("Call to abstract method \(Self.self).\(#function)")
func out(_ aStr: String) throws
preconditionFailure("Call to abstract method \(Self.self).\(#function)")
// Other stuff
class CompileContext: AbstractContext
required init() throws
override func out(_ aStr: String) throws
print(aStr)
// Other stuff
这是我删除 CompileContext.out
后得到的结果:
Fatal error: Call to abstract method CompileContext.out(_:): file swiftpg/contexts.swift, line 28
【讨论】:
【参考方案9】:由于没有动态调度的限制,你可以这样做:
import Foundation
protocol foo
static var instance: foo? get
func prt()
extension foo
func prt()
if Thread.callStackSymbols.count > 30
print("super")
else
Self.instance?.prt()
class foo1 : foo
static var instance : foo? = nil
init()
foo1.instance = self
func prt()
print("foo1")
class foo2 : foo
static var instance : foo? = nil
init()
foo2.instance = self
func prt()
print("foo2")
class foo3 : foo
static var instance : foo? = nil
init()
foo3.instance = self
var f1 : foo = foo1()
f1.prt()
var f2 : foo = foo2()
f2.prt()
var f3 : foo = foo3()
f3.prt()
【讨论】:
【参考方案10】:我试图创建一个Weather
抽象类,但使用协议并不理想,因为我不得不一遍又一遍地编写相同的init
方法。扩展协议并编写 init
方法存在问题,特别是因为我使用符合 NSCoding
的 NSObject
。
所以我想出了这个NSCoding
一致性:
required init?(coder aDecoder: NSCoder)
guard type(of: self) != Weather.self else
fatalError("<Weather> This is an abstract class. Use a subclass of `Weather`.")
// Initialize...
至于init
:
fileprivate init(param: Any...)
// Initialize
【讨论】:
【参考方案11】:将所有对 Base 类的抽象属性和方法的引用移动到协议扩展实现,其中 Self 约束到 Base 类。您将可以访问基类的所有方法和属性。此外,编译器检查派生类协议中抽象方法和属性的实现
protocol Commom:class
var tableView:UITableView get;
func update();
class Base
var total:Int = 0;
extension Common where Self:Base
func update()
total += 1;
tableView.reloadData();
class Derived:Base,Common
var tableView:UITableView
return owner.tableView;
【讨论】:
以上是关于Swift 语言中的抽象类的主要内容,如果未能解决你的问题,请参考以下文章