Вызов реализации протокола по умолчанию из обычного метода

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.

Cojoj
источник
Я бы использовал 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
Разве это не приводит к бесконечному циклу?
Стэн Лю
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