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)

所以基本上你得到:

    编译时检查保证所有Drivables 实现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 方法存在问题,特别是因为我使用符合 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

【讨论】:

【参考方案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 语言中的抽象类的主要内容,如果未能解决你的问题,请参考以下文章

Objective-C基础知识之“类”

Java中的抽象类

java——抽象类

分清java中的接口和抽象类

第52课 C++中的抽象类和接口

Java中的抽象类和接口