Виклик реалізації протоколу за замовчуванням із звичайного методу


83

Цікаво, чи можливо досягти такого?
У мене є такий дитячий майданчик:

protocol Foo {
    func testPrint()
}

extension Foo {
    func testPrint() {
        print("Protocol extension call")
    }
}

struct Bar: Foo {
    func testPrint() {
        // Calling self or super go call default implementation
        self.testPrint()
        print("Call from struct")
    }
}


let sth = Bar()
sth.testPrint()

Я можу надати реалізацію за замовчуванням, extensionале що, якщо Barпотрібно все, що є в реалізації за замовчуванням, а також додаткові речі?
Це якось схоже на виклик super.методів в classes для виконання вимоги щодо реалізації кожного властивості тощо, але я не бачу можливості досягти того самого за допомогою structs.


Я б використав Foo.testPrint(self)()- проблема в тому, що він виходить з ладу через помилку сегментації (протестовано як на 7.0 GM, так і на 7.1 бета-версії)
Антоніо

1
Це дивна конструкція, яку ви представили 😯
cojoj

4
Кожен метод екземпляра - це статичний метод з використанням екземпляра як перший параметр
Антоніо

Однак я спробував видалити розширення, і воно видає ту саму помилку сегментації. Можливо, це не повинно працювати з протоколами
Антоніо

Хм, соромно, що я повинен повторити себе в коді, хоча це можна легко виправити за допомогою реалізації за замовчуванням ...
cojoj

Відповіді:


90

Я не знаю, чи все ще ви шукаєте відповідь на це, але спосіб це зробити - це вилучити функцію з визначення протоколу, передати свій об’єкт, Fooа потім викликати на ньому метод:

protocol Foo { 
    // func testPrint() <- comment this out or remove it
}

extension Foo {
    func testPrint() {
        print("Protocol extension call")
    }
}

struct Bar: Foo {
    func testPrint() {
        print("Call from struct")
        (self as Foo).testPrint() // <- cast to Foo and you'll get the  default
                                  //    function defined in the extension
    }
}

Bar().testPrint()

// Output:    "Call from struct"
//            "Protocol extension call"

З якихось причин вона працює лише в тому випадку, якщо функція не оголошена як частина протоколу, а визначена у розширенні протоколу. Піди розберися. Але це працює.


1
О, Боже! Я не можу повірити, що вони змушують вас вилучити функцію з протоколу! гарна відповідь, дякую!
SkyWalker

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

15
Це насправді кардинально змінює семантику протоколу + розширення. Якщо залишити декларацію поза протоколом, ви отримаєте статичну розсилку при виклику функції типу, який відповідає протоколу - ось чому ви можете привести і отримати реалізацію з розширення. Якщо ви додасте декларацію до протоколу, ваш виклик функції буде динамічно відправлятися.
Торстен Каррер

2
Це працює, лише якщо Fooпротокол не успадковується від будь-якого іншого протоколу.
iyuna

4
Хіба це не веде до нескінченної петлі?
stan liu

9

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

struct Bar: Foo {
    func testPrint() {
        // Calling default implementation
        struct Dummy : Foo {}
        let dummy = Dummy()
        dummy.testPrint()
        print("Call from struct")
    }
}

1
Схоже, це єдина можливість зараз (підтверджена Apple) ... Я подам функціональний радар для цього, оскільки він може бути корисним 👌
cojoj

4

Дякую за допис! Якщо ви помістите визначення функції в протокол, тоді, коли об'єкт буде відлитий як протокол, він бачить лише версію функції об'єкта, і оскільки ви викликаєте його всередину себе, ви отримуєте нову адресу Apple ...

Я спробував таку версію:

import UIKit
protocol MyProc
{
}

protocol MyFuncProc
{
    func myFunc()
}

extension MyProc
{
    func myFunc()
    {
        print("Extension Version")
    }
}

struct MyStruct: MyProc, MyFuncProc
{
    func myFunc()
    {
        print("Structure Version")
        (self as MyProc).myFunc()
    }
}

(MyStruct() as MyFuncProc).myFunc()

Це дає результат:

Structure Version
Extension Version

3

У разі , якщо ваш протокол має associatedTypeабо Selfвимоги, то приведення не працюватиме. Щоб обійти це, створіть "тіньову" реалізацію за замовчуванням, яку можуть викликати як звичайна реалізація за замовчуванням, так і відповідний тип.

protocol Foo { 
    associatedType Bar
}

extension Foo {
    func testPrint() {
        defaultTestPrint()
    }
}

fileprivate extension Foo { // keep this as private as possible
    func defaultTestPrint() {
        // default implementation
    }
}

struct Bar: Foo {
    func testPrint() {
        // specialized implementation
        defaultTestPrint()
    }
}

Не можу з вами погодитись більше. defaultXX()є набагато виразнішим і читабельнішим за інші відповіді.
DawnSong

І я думаю, що Амін Мадані покращив вашу відповідь.
DawnSong

2

що ви думаєте про такий спосіб виправити це?

protocol Foo {
    func testPrint()
}

extension Foo {
    func testPrint() {
        defaultTestPrint()
    }

    func defaultTestPrint() {
        print("Protocol extension call")
    }
}

struct Bar: Foo {
    func testPrint() {
        // Calling self or super go call default implementation
        defaultTestPrint()
        print("Call from struct")
    }
}


let sth = Bar()
sth.testPrint()

Це відповідь?
Anh Pham,

@AnhPham ось це, просто ще один метод для виконання функцій за замовчуванням
Амін Мадані

кмітливе та легке рішення
Стефан

Найвиразніша і гнучка відповідь.
DawnSong

2

Я придумав рішення для цього.

Проблема

Коли у вас є реалізація за замовчуванням у розширенні, коли ви реалізуєте протокол до іншого класу / структури, ви реалізуєте цю реалізацію за замовчуванням, якщо реалізуєте метод. Це за задумом, так працюють протоколи

Рішення

  • Створіть реалізацію вашого протоколу за замовчуванням і зробіть це властивістю вашого протоколу.
  • Потім, коли ви реалізуєте цей протокол у класі, надайте реалізацію за замовчуванням за допомогою геттера
  • Зателефонуйте за замовчуванням, коли вам потрібно.

Приклад


protocol Foo {
    var defaultImplementation: DefaultImpl? { get }
    func testPrint()
}

extension Foo {
    // Add default implementation
    var defaultImplementation: DefaultImpl? {
        get {
            return nil
        }
    }
}

struct DefaultImpl: Foo {
    func testPrint() {
        print("Foo")
    }
}


extension Foo {
    
    func testPrint() {
        defaultImplementation?.testPrint()
    }
}

struct Bar: Foo {
    
    var defaultImplementation: DefaultImpl? {
        get { return DefaultImpl() }
    }
    func testPrint() {
        if someCondition {
            defaultImplementation?.testPrint() // Prints "Foo"
        }
    }
}

struct Baz: Foo {
    func testPrint() {
        print("Baz")
    }
}


let bar = Bar()
bar.testPrint() // prints "Foo"

let baz = Baz()
baz.testPrint() // prints "Baz"


Недоліки

Ви втрачаєте необхідну помилку реалізації в структурі / класі, де ви реалізуєте цей протокол.


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

Так, це хороша реалізація!
juxhin bleta
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.