Domyślna implementacja protokołu wywołania ze zwykłej metody

83

Zastanawiam się, czy da się coś takiego osiągnąć.
Mam taki plac zabaw:

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()

Mogę zapewnić domyślną implementację w, extensionale co zrobić, jeśli Barpotrzebuję wszystkiego, co jest w domyślnej implementacji oraz dodatkowych rzeczy?
Jest to trochę podobne do wywoływania super.metod w classes w celu spełnienia wymagań implementacji każdej właściwości itp., Ale nie widzę możliwości osiągnięcia tego samego z structs.

cojoj
źródło
Użyłbym Foo.testPrint(self)()- problem polega na tym, że nie działa z powodu błędu segmentacji (testowane zarówno na 7.0 GM, jak i 7.1 beta)
Antonio
1
To dziwna konstrukcja, którą przedstawiłeś 😯
cojoj,
4
Każda metoda instancji jest statyczną metodą curried, której pierwszym parametrem jest instancja
Antonio
Jednak próbowałem usunąć rozszerzenie i zgłasza ten sam błąd segmentacji. Prawdopodobnie to nie powinno działać z protokołami
Antonio
Hmmm, szkoda, że ​​muszę powtórzyć się w kodzie, podczas gdy można to łatwo naprawić za pomocą domyślnej implementacji ...
cojoj

Odpowiedzi:

90

Nie wiem, czy nadal szukasz odpowiedzi na to pytanie, ale sposobem na to jest usunięcie funkcji z definicji protokołu, przerzucenie obiektu na obiekt, Fooa następnie wywołanie na nim metody:

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"

Z jakiegoś powodu działa tylko wtedy, gdy funkcja nie jest zadeklarowana jako część protokołu, ale jest zdefiniowana w rozszerzeniu protokołu. Domyśl. Ale to działa.

Aaron Rasmussen
źródło
1
O Boże! Nie mogę uwierzyć, że zmuszają cię do usunięcia funkcji z protokołu! dobra odpowiedź, dziękuję!
SkyWalker
5
Dla mnie wygląda to na błąd, nie powinno być możliwe uzyskanie innej implementacji metody poprzez rzutowanie tej samej instancji.
MANIAK_dobrii
15
To faktycznie drastycznie zmienia semantykę protokołu + rozszerzenie. Jeśli pozostawisz deklarację poza protokołem, otrzymasz statyczną wysyłkę podczas wywoływania funkcji na typie zgodnym z protokołem - dlatego możesz rzutować i pobierać implementację z rozszerzenia. Jeśli dodasz deklarację do protokołu, wywołanie funkcji zostanie wysłane dynamicznie .
Thorsten Karrer
2
Działa to jednak tylko wtedy Foo, gdy protokół nie dziedziczy z żadnego innego protokołu.
iyuna
4
Czy to nie prowadzi do nieskończonej pętli?
stan liu
9

Cóż, możesz utworzyć zagnieżdżony typ zgodny z protokołem, utworzyć jego instancję i wywołać metodę na tym (nie ma znaczenia, że ​​nie możesz uzyskać dostępu do danych swojego typu, ponieważ implementacja wewnątrz rozszerzenia protokołu i tak nie może się do niego odwoływać). Ale to nie jest rozwiązanie, które nazwałbym eleganckim.

struct Bar: Foo {
    func testPrint() {
        // Calling default implementation
        struct Dummy : Foo {}
        let dummy = Dummy()
        dummy.testPrint()
        print("Call from struct")
    }
}
Thorsten Karrer
źródło
1
Wygląda na to, że w tej chwili jest to jedyna możliwość (potwierdzona przez Apple) ... Złożę radar funkcji dla tego, ponieważ może się przydać 👌
cojoj
4

Dzięki za post! Jeśli umieścisz definicję funkcji w protokole, to gdy obiekt jest rzutowany jako protokół, widzi on tylko wersję funkcji obiektu, a ponieważ wywołujesz ją w sobie, otrzymasz nowy adres Apple ...

Wypróbowałem wersję taką jak ta:

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()

Daje to wynik:

Structure Version
Extension Version
Jim Malak
źródło
3

Jeśli twój protokół ma associatedTypelub Selfwymagania, obsada nie będzie działać. Aby obejść ten problem, utwórz domyślną implementację „cienia”, którą może wywoływać zarówno zwykła domyślna implementacja, jak i zgodny typ.

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()
    }
}
David James
źródło
Nie mogę się z tobą bardziej zgodzić. defaultXX()jest dużo bardziej wyrazisty i czytelny niż inne odpowiedzi.
DawnSong
Myślę, że Amin Madani poprawił twoją odpowiedź.
DawnSong
2

co myślisz o takim sposobie naprawienia tego?

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()
Amin Madani
źródło
Czy to jest odpowiedź?
Anh Pham
@AnhPham to wszystko, po prostu kolejna metoda wykonania domyślnej funkcjonalności
Amin Madani
Cleaver i łatwe rozwiązanie
Stephan Januar
Najbardziej wyrazista i elastyczna odpowiedź.
DawnSong
2

Mam na to rozwiązanie.

Kwestia

Jeśli masz domyślną implementację w rozszerzeniu, podczas implementowania protokołu do innej klasy / struktury, tracisz tę domyślną implementację, jeśli zaimplementujesz metodę. Jest to zgodne z projektem, tak działają protokoły

Rozwiązanie

  • Utwórz domyślną implementację swojego protokołu i ustaw ją jako właściwość swojego protokołu.
  • Następnie, gdy implementujesz ten protokół w klasie, podaj domyślną implementację za pomocą metody pobierającej
  • W razie potrzeby wywołaj domyślną implementację.

Przykład


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"


Wady

Tracisz wymagany błąd implementacji w strukturze / klasie, w której implementujesz ten protokół.

Andi Beqiri
źródło
1
Pamiętaj, aby wyjaśnić, dlaczego Twój kod działa. Pomaga to innym, którzy odwiedzają Twoją odpowiedź, uczyć się z niej.
AlexH
Tak, to dobra realizacja!
juxhin bleta