스위프트 언어의 추상 클래스


141

Swift Language에서 추상 클래스를 만드는 방법이 있습니까, 아니면 Objective-C와 같은 제한입니까? Java가 추상 클래스로 정의한 것과 비슷한 추상 클래스를 만들고 싶습니다.


전체 클래스가 추상적이거나 그 안에 몇 가지 메소드가 필요합니까? 단일 방법 및 속성에 대한 답변은 여기를 참조하십시오. stackoverflow.com/a/39038828/2435872 . Java에서는 추상 메소드가없는 추상 클래스를 사용할 수 있습니다. 이 특수 기능은 Swift에서 제공하지 않습니다.
jboi

답변:


175

Swift에는 Objective-C와 같은 추상 클래스가 없습니다. 최선의 방법은 Java 인터페이스와 같은 Protocol 을 사용하는 것 입니다.

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

이것은 구조체에서도 "추상 클래스"기능을 제공하지만 클래스는 동일한 프로토콜을 구현할 수 있습니다.

또한 직원 프로토콜을 구현하는 모든 클래스 또는 구조체는 annualSalary 속성을 다시 선언해야합니다.

가장 중요한 것은 동적 디스패치가 없다는 것입니다 . 때 logSalaryA와 저장되어있는 인스턴스라고 SoftwareEngineer는 메소드의 오버라이드 (override) 버전을 호출합니다. 때 logSalary그가에 캐스팅 된 후 인스턴스라고 Employee, 그것은 인스턴스가 실제로 비록 그렇지 동적으로 오버라이드 (override) 버전으로 전달하지 않습니다 (원래 구현을 호출합니다 Software Engineer.

자세한 내용은 해당 기능에 대한 훌륭한 WWDC 비디오를 확인하십시오. Swift에서 가치 유형으로 더 나은 앱 구축


3
protocol Animal { var property : Int { get set } }. 부동산이 세터를 갖기를 원하지 않는 경우에도 세트를 남겨 둘 수 있습니다
drewag

3
이 wwdc 비디오 가 더 관련 있다고 생각 합니다
Mario Zannone

2
@MarioZannone 그 비디오는 내 마음을 날려 버렸고 Swift와 사랑에 빠졌습니다.
Scott H

3
func logSalary()Employee 프로토콜 선언에 추가 하면이 예제는에 overridden대한 두 호출 모두에 대해 인쇄 합니다 logSalary(). 이것은 스위프트 3.1에 있습니다. 따라서 다형성의 이점을 얻을 수 있습니다. 두 경우 모두 올바른 방법이 호출됩니다.
Mike Taverne

1
동적 디스패치에 대한 규칙은 다음과 같습니다. 메소드가 확장 에서만 정의 된 경우 정적으로 디스패치됩니다. 확장중인 프로토콜에도 정의되어 있으면 동적으로 전달됩니다. Objective-C 런타임이 필요하지 않습니다. 이것은 순수한 스위프트 동작입니다.
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. Drivable( accelerate) 를 준수하는 모든 유형에 대해 기본 동작을 구현할 수 있습니다.
  3. Drivable 그것은 단지 프로토콜이기 때문에 인스턴스화되지 않도록 보장됩니다.

이 모델은 실제로는 특성과 훨씬 유사하게 작동하므로 여러 프로토콜을 준수하고 프로토콜의 기본 구현을 수행 할 수 있지만 추상 슈퍼 클래스에서는 간단한 클래스 계층 구조로 제한됩니다.


여전히 일부 프로토콜을 확장 할 수있는 것은 아닙니다 (예 :) UICollectionViewDatasource. 모든 상용구를 제거하고 별도의 프로토콜 / 확장으로 캡슐화 한 다음 여러 클래스에서 재사용하고 싶습니다. 사실, 템플릿 패턴은 ... 여기 완벽하지만 것
리처드 Topchii

1
˚Car˚에서 ˚ 가속을 덮어 쓸 수 없습니다. 그렇게하면 컴파일러 확장없이 ˚extentsion Driveable˚의 구현이 계속 호출됩니다. 매우 자바 추상 클래스와는 달리
게르트 Castan

@GerdCastan 사실, 프로토콜 확장은 동적 디스패치를 ​​지원하지 않습니다.
IluTov

15

나는 이것이 Java abstract또는 C #에 가장 가깝다고 생각합니다 abstract.

class AbstractClass {

    private init() {

    }
}

다음 사항을 참고 위해서는 private작업을 수정, 별도의 스위프트 파일에이 클래스를 정의해야합니다.

편집 : 여전히이 코드는 추상 메소드를 선언 할 수 없으므로 구현을 강제합니다.


4
그럼에도 불구하고 이것은 서브 클래스가 함수를 재정의하는 동안 부모 클래스에서 해당 함수의 기본 구현을 강제 하지 않습니다 .
Matthew Quiros

C #에서 추상 기본 클래스에서 함수를 구현하면 해당 서브 클래스에서 강제로 구현하지 않아도됩니다. 여전히,이 코드는 강제로 재정의하기 위해 추상 메소드를 선언 할 수 없습니다.
Teejay

ConcreteClass가 해당 AbstractClass를 서브 클래스로 만들 수 있습니다. ConcreteClass를 어떻게 인스턴스화합니까?
Javier Cadiz

2
ConcreteClass에는 공개 생성자가 있어야합니다. 동일한 파일에 있지 않으면 AbstractClass에 보호 생성자가 필요할 것입니다. 내가 기억하는대로 Swift에는 보호 된 액세스 수정자가 없습니다. 따라서 해결책은 ConcreteClass를 동일한 파일로 선언하는 것입니다.
Teejay

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

@Angad 델리게이트 패턴은 동일한 사용 사례이지만 번역은 아닙니다. 패턴이 다르므로 다른 관점을 취해야합니다.
Josh Woodcock

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

추상 클래스를 구현하는 또 다른 방법은 초기화 프로그램을 차단하는 것입니다. 나는 이렇게했다 :

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

@Cristik 나는 주요 아이디어를 보여주었습니다. 완벽한 해결책은 아닙니다. 이런 식으로 당신은 당신의 상황에 대해 충분히 상세하지 않기 때문에 80 %의 답변을 싫어할 수 있습니다
Alexey Yarmolovich

1
@AlexeyYarmolovich 누가 답의 80 %를 싫어하지 않습니까? :) 농담을 제외하고, 나는 당신의 모범이 향상 될 수 있다고 제안하고 있습니다.
Cristik

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 클래스의 추상 속성 및 메소드에 대한 모든 참조를 프로토콜 확장 구현으로 이동하십시오. 여기서 Self constraint는 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.