Swift言語の抽象クラス


140

Swift言語で抽象クラスを作成する方法はありますか、それともObjective-Cと同様に制限ですか?Javaが抽象クラスとして定義するものに匹敵する抽象クラスを作成したいと思います。


完全なクラスが抽象的である必要がありますか、それともいくつかのメソッドだけが必要ですか?単一のメソッドとプロパティについては、こちらの回答を参照してください。stackoverflow.com/a/39038828/2435872。Javaでは、抽象メソッドを持たない抽象クラスを作成できます。その特別な機能はSwiftでは提供されていません。
jboi

回答:


174

Swiftには抽象クラスはありません(Objective-Cと同様)。あなたの最善の策は、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プロパティを再度宣言する必要があることにも注意してください。

最も重要なのは、動的なディスパッチがないことです。場合logSalaryとして記憶されているインスタンスで呼び出されSoftwareEngineer、それがメソッドのオーバーライドされたバージョンを呼び出します。にlogSalaryキャストされた後にインスタンスで呼び出されるEmployeeと、元の実装が呼び出されます(インスタンスが実際にはであっても、オーバーライドされたバージョンに動的にディスパッチされませんSoftware Engineer

詳細については、その機能に関する優れたWWDCビデオを確認してください:Swiftでの値タイプによるより良いアプリの構築


3
protocol Animal { var property : Int { get set } }。プロパティにセッターを設定したくない場合は、セットを
省略

3
私はこのwwdcビデオがさらに関連していると思います
マリオ

2
@MarioZannoneそのビデオは私の心を吹き飛ばし、私をスウィフトに恋にさせました。
Scott H

3
func logSalary()Employeeプロトコル宣言に追加するだけの場合、例ではoverriddenへの両方の呼び出しが出力されますlogSalary()。これはSwift 3.1にあります。したがって、ポリモーフィズムの利点が得られます。どちらの場合も、正しいメソッドが呼び出されます。
Mike Taverne、

1
動的ディスパッチに関するルールはこれです...メソッドが拡張機能でのみ定義されている場合、静的にディスパッチされます。拡張するプロトコルでも定義されている場合は、動的にディスパッチされます。Objective-Cランタイムは必要ありません。これは純粋なSwiftの動作です。
Mark A. Donohoe

47

この回答は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)

つまり、基本的には次のようになります。

  1. すべてを保証するコンパイル時間チェック Drivableのが実装speed
  2. 準拠するすべてのタイプにデフォルトの動作を実装できます Drivableaccelerate)に
  3. Drivable これは単なるプロトコルであるため、インスタンス化されないことが保証されています

このモデルは実際にはトレイトのように動作します。つまり、複数のプロトコルに準拠し、それらのいずれかのデフォルト実装を実行できますが、抽象スーパークラスでは単純なクラス階層に制限されます。


それでも、必ずしも一部のプロトコルを拡張する可能性があるとは限りませんUICollectionViewDatasource。すべてのボイラープレートを削除し、個別のプロトコル/拡張機能にカプセル化してから、複数のクラスで再利用したいと思います。実際、ここではテンプレートパターンは完璧ですが、
Richard Topchii '06 / 10/06

1
「Car」で「accelerate」を上書きすることはできません。その場合、「拡張ドライブ可能」の実装は、コンパイラの警告なしに呼び出されます。非常にJavaの抽象クラスとは異なり
ゲルトCastan

@GerdCastan確かに、プロトコル拡張は動的ディスパッチをサポートしていません。
IluTov 2017年

15

これはJava abstractまたはC#に最も近いと思いますabstract

class AbstractClass {

    private init() {

    }
}

なお、 private修飾子が機能このクラスを別のSwiftファイルで定義する必要が。

編集:それでも、このコードは抽象メソッドを宣言することを許可しないため、その実装を強制します。


4
それでも、これはサブクラスに関数のオーバーライドを強制するのではなく、親クラスにその関数の基本実装も持っています。
Matthew Quiros、2015年

C#では、抽象基本クラスに関数を実装しても、そのサブクラスに関数を実装する必要はありません。それでも、このコードでは、オーバーライドを強制するために抽象メソッドを宣言することはできません。
Teejay、2015年

ConcreteClassがそのAbstractClassのサブクラスだとしましょう。ConcreteClassをどのようにインスタンス化しますか?
ハビエルカディス

2
ConcreteClassにはパブリックコンストラクターが必要です。同じファイル内にある場合を除き、AbstractClassで保護されたコンストラクタが必要になる可能性があります。私が覚えているように、保護されたアクセス修飾子はSwiftには存在しません。したがって、解決策は、同じファイルでConcreteClassを宣言することです。
Teejay、2016年

13

最も簡単な方法は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プロトコル宣言に含まれています。そうしないと、呼び出しがクラッシュします。
Mike Taverne

11

数週間苦労した後、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());
        }
    }

viewWillAppearなどのUITableViewControllerのいくつかのメソッドを共通化したかったため、この種のパターンが必要でした。これは役に立ちましたか?


1
+1あなたが最初に述べたのと全く同じアプローチをするつもりだった。委任パターンへの興味深いポインタ。
Angad

また、両方の例が同じユースケースである場合にも役立ちます。GoldenSpoonChildは、特に母親が拡張しているように見えるため、やや紛らわしい名前です。
Angad、2015年

@Angadデリゲートパターンは同じ使用例ですが、翻訳ではありません。それは異なるパターンなので、異なる視点をとらなければなりません。
ジョシュウッドコック2017

8

プロトコルを使用して抽象クラスをシミュレートする方法があります。これは例です:

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

1

抽象クラスを実装するもう1つの方法は、初期化子をブロックすることです。私はそれをこのようにしました:

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

4
これは保証やチェックを提供しません。実行時に爆破することは、ルールを強制するための悪い方法です。initをプライベートとして使用することをお勧めします。
Морт

抽象クラスは、抽象メソッドもサポートする必要があります。
Cristik、2016年

@Cristik私は主なアイデアを示しました、それは完全な解決策ではありません。このように、状況の詳細が十分でないため、回答の80%を嫌うことができます
Alexey Yarmolovich

1
@AlexeyYarmolovich私が答えの80%を嫌っていないと言っているのは誰ですか?:)冗談はさておき、私はあなたの例を改善できることを提案しました。これは他の読者を助け、賛成票を集めることであなたを助けます。
Cristik 2016年

0

Weather抽象クラスを作ろうとしていましたが、同じinitメソッドを何度も書く必要があったため、プロトコルの使用は理想的ではありませんでした。init特ににNSObject準拠して使用していたため、プロトコルの拡張とメソッドの記述には問題がありましたNSCoding

だから私は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
}

0

Baseクラスの抽象プロパティおよびメソッドへのすべての参照を、自己拡張がBaseクラスにあるプロトコル拡張実装に移動します。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;
  }
}

0

動的ディスパッチがないという制限により、次のようなことができます。

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()
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.