ChatGPT解决这个技术问题 Extra ChatGPT

Swift 语言中的抽象类

有没有办法在 Swift 语言中创建一个抽象类,或者这是像 Objective-C 一样的限制?我想创建一个与 Java 定义为抽象类的抽象类相当。

你需要整个类是抽象的还是只是其中的一些方法?有关单个方法和属性,请参见此处的答案。 stackoverflow.com/a/39038828/2435872 。在 Java 中,您可以拥有没有抽象方法的抽象类。 Swift 不提供该特殊功能。

C
Calvin Chen

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 行为。
I
IluTov

请注意,此答案针对 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 实现速度 您可以为所有符合 Drivable 的类型实现默认行为(加速) Drivable 保证不会被实例化,因为它只是一个协议

这个模型实际上更像特征,这意味着您可以遵循多个协议并采用其中任何一个的默认实现,而对于抽象超类,您仅限于简单的类层次结构。


尽管如此,并不总是有可能扩展某些协议,例如 UICollectionViewDatasource。我想删除所有样板并将其封装在单独的协议/扩展中,然后由多个类重新使用。事实上,模板模式在这里会很完美,但是......
您不能在 ˚Car˚ 中覆盖 ˚accelerate˚。如果你这样做了,在没有任何编译器警告的情况下仍然会调用 ˚extentsion Driveable˚ 中的实现。与 Java 抽象类非常不同
@GerdCastan 是的,协议扩展不支持动态调度。
T
Teejay

我认为这是最接近 Java 的 abstract 或 C# 的 abstract

class AbstractClass {

    private init() {

    }
}

请注意,为了使 private 修饰符起作用,您必须在单独的 Swift 文件中定义此类。

编辑:仍然,此代码不允许声明抽象方法并因此强制其实现。


尽管如此,这并不强制子类覆盖一个函数,同时在父类中也有该函数的基本实现。
在 C# 中,如果您在抽象基类中实现函数,则不必在其子类中实现它。尽管如此,此代码仍不允许您声明抽象方法以强制覆盖。
可以说 ConcreteClass 是 AbstractClass 的子类。你如何实例化 ConcreteClass ?
ConcreteClass 应该有一个公共构造函数。您可能需要 AbstractClass 中的受保护构造函数,除非它们在同一个文件中。据我记得,受保护的访问修饰符在 Swift 中不存在。所以解决方案是在同一个文件中声明ConcreteClass。
C
Carlos García

最简单的方法是使用对 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;否则通话会崩溃。
J
Josh Woodcock

经过几周的努力,我终于意识到如何将一个 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委托模式是相同的用例,但它不是翻译;这是一个不同的模式,所以它必须采取不同的观点。
D
David Seca

有一种使用协议模拟抽象类的方法。这是一个例子:

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

A
Alexey Yarmolovich

实现抽象类的另一种方法是阻止初始化程序。我是这样做的:

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% 的答案? :) 开个玩笑,我建议您的示例可以改进,这将帮助其他读者,并通过获得支持来帮助您。
i
ish-west

这是一个非常古老的问题,但仍然......这是在 Swift 5.2 上编译并按预期工作的实际代码片段:

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

C
Christo Smal

由于没有动态调度的限制,您可以执行以下操作:

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

f
funct7

我试图创建一个 Weather 抽象类,但使用协议并不理想,因为我必须一遍又一遍地编写相同的 init 方法。扩展协议并编写 init 方法存在问题,特别是因为我使用的是符合 NSCodingNSObject

所以我想出了这个 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
}

j
john07

将对基类的抽象属性和方法的所有引用移动到协议扩展实现,其中自我约束到基类。您将可以访问基类的所有方法和属性。此外,编译器检查派生类协议中抽象方法和属性的实现

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