Анотаційні заняття зі швидкої мови


141

Чи є спосіб створити абстрактний клас у Swift Language, чи це обмеження так само, як Objective-C? Я хотів би створити абстрактний клас, порівнянний з тим, що Java визначає як абстрактний клас.


Вам потрібен повний клас, щоб бути абстрактним або просто деякі методи в ньому? Тут ви знайдете відповідь щодо окремих методів та властивостей. stackoverflow.com/a/39038828/2435872 . У Java ви можете відмовитись від абстрактних класів, які не мають жодного із методів абстрактних. Ця особливість не надається Swift.
jboi

Відповіді:


175

У 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, доведеться знову оголосити властивість щорічної заробітної плати.

Найголовніше, зауважте, що немає динамічної відправки . Коли logSalaryвикликається екземпляр, який зберігається як a, SoftwareEngineerвін викликає перезаписану версію методу. Коли logSalaryвикликається екземпляр після того, як він був переданий до Employee, він викликає оригінальну реалізацію (вона не динамічно передається до перекритої версії, хоча екземпляр насправді є Software Engineer.

Для отримання додаткової інформації перегляньте чудове відео WWDC про цю особливість: Створення кращих програм із типовими значеннями в Swift


3
protocol Animal { var property : Int { get set } }. Ви також можете пропустити цей набір, якщо ви не хочете, щоб у
власнику

3
Я думаю, що це відео wwdc є ще більш актуальним
Маріо Заннон

2
@MarioZannone це відео просто підірвало мою думку і змусило мене закохатись у Свіфта.
Скотт Н

3
Якщо ви просто додаєте func logSalary()до декларації протоколу працівника, приклад друкує overriddenдля обох дзвінків до logSalary(). Це в Swift 3.1. Таким чином ви отримуєте переваги поліморфізму. Правильний метод називається в обох випадках.
Майк Таверн

1
Правило щодо динамічної відправки таке: якщо метод визначений лише в розширенні, то він відправляється статично. Якщо це також визначено в розширеному протоколі, він динамічно розсилається. Немає необхідності в режимі виконання Objective-C. Це чиста поведінка Свіфта.
Марк А. Донохое

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. Скомпілюйте перевірки часу, які гарантують виконання всіх Drivablesspeed
  2. Ви можете реалізувати поведінку за замовчуванням для всіх типів, які відповідають Drivable( accelerate)
  3. Drivable гарантується, що не буде моментувати, оскільки це просто протокол

Ця модель насправді поводиться набагато більше, як риси, тобто ви можете відповідати декільком протоколам і приймати за замовчуванням реалізацію будь-якого з них, тоді як з абстрактним суперкласом ви обмежені простою ієрархією класів.


Тим НЕ менше, не завжди є можливість розширити деякі протоколи, наприклад, UICollectionViewDatasource. Я хотів би зняти всю котлову та інкапсулювати її в окремий протокол / розширення, а потім повторно використовувати декілька класів. Насправді, шаблон шаблону був би тут ідеальним, але ...
Річард Топчій

1
Ви не можете перезаписати "прискорення" в "Car". Якщо це зробити, реалізація в "Extentsion Driveable" все ще викликається без попередження компілятора. На відміну від абстрактного класу Java
Герд Кастан

@GerdCastan True, розширення протоколу не підтримують динамічну розсилку.
IluTov

15

Я думаю, що це найближче до Java abstractабо C # abstract:

class AbstractClass {

    private init() {

    }
}

Зауважте, що для роботи privateмодифікаторів ви повинні визначити цей клас в окремому файлі Swift.

EDIT: Тим не менш, цей код не дозволяє оголосити абстрактний метод і, таким чином, змусити його здійснити.


4
Тим не менш, це не змушує підклас переосмислювати функцію, а також мати базову реалізацію цієї функції в батьківському класі.
Метью Кірос

У C #, якщо ви реалізуєте функцію в абстрактному базовому класі, ви не змушені реалізовувати її у її підкласах. Тим не менш, цей код не дозволяє оголосити абстрактний метод з метою примусового переопрацювання.
Teejay

Скажемо, що підклас ConcreteClass, що абстрактний клас. Як інстанціювати ConcreteClass?
Хав'єр Кадіс

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в декларацію протоколу; інакше виклик завершується.
Майк Таверн

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 не реалізувала абстрактні класи, тому що зазвичай використовує схему протоколів delegate +. Наприклад, таку ж схему вище було б краще зробити так:

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 тощо. Чи корисно це було?


1
+1 планував зробити той самий підхід, який ви спочатку згадуєте; цікавий вказівник на схему делегування.
Ангад

Крім того, це допоможе, якби обидва ваші приклади були на одному і тому ж випадку використання. GoldenSpoonChild - трохи заплутане ім’я, особливо зважаючи на те, що мама, здається, поширює його.
Агад

@Angad Делегатний шаблон є тим самим випадком використання, однак це не переклад; це інший зразок, тому він повинен сприймати іншу точку зору.
Джош Вуддок

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
Це не дає жодних гарантій та / або чеків. Вибух під час виконання - це поганий спосіб виконання правил. Краще мати ініт як приватний.
Морт

Абстрактні заняття також повинні мати підтримку абстрактних методів.
Крістік

@ Cristik Я показав головну ідею, це не повне рішення. Таким чином ви можете не сподобатися 80% відповідей, оскільки вони недостатньо деталізовані для вашої ситуації
Олексій Ярмолович

1
@AlexeyYarmolovich, хто каже, що я не люблю 80% відповідей? :) Жартуючи вбік, я припускав, що ваш приклад можна вдосконалити, це допоможе іншим читачам, і допоможе вам, отримавши резюме.
Крістік

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()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.