Jak zdefiniować opcjonalne metody w protokole Swift?

359

Czy jest to możliwe w Swift? Jeśli nie, to czy istnieje obejście tego problemu?

Selvin
źródło
1
W końcu zmotywowałem się do aktualizacji mojej odpowiedzi, możesz ponownie rozważyć jej zaakceptowanie.
akashivskyy
@akashivskyy Przyjmuję twoją odpowiedź, ponieważ lepiej prezentuje wszystkie dostępne opcje oraz ich zalety i wady.
Selvin

Odpowiedzi:

503

1. Korzystanie z domyślnych implementacji (preferowane).

protocol MyProtocol {
    func doSomething()
}

extension MyProtocol {
    func doSomething() {
        /* return a default value or just leave empty */
    }
}

struct MyStruct: MyProtocol {
    /* no compile error */
}

Zalety

  • Nie jest zaangażowane żadne środowisko wykonawcze Objective-C (cóż, przynajmniej nie ma wyraźnego). Oznacza to, że możesz dostosować do niego struktury, wyliczenia i nieklasy NSObject. Oznacza to również, że możesz skorzystać z potężnego systemu generycznego.

  • Zawsze możesz być pewien, że wszystkie wymagania są spełnione, gdy napotkasz typy zgodne z takim protokołem. Zawsze jest to albo konkretna implementacja, albo domyślna. Tak zachowują się „interfejsy” lub „umowy” w innych językach.

Niedogodności

  • W przypadku braku Voidwymagań musisz mieć rozsądną wartość domyślną , co nie zawsze jest możliwe. Jednak gdy napotkasz ten problem, oznacza to, że albo taki wymóg nie powinien naprawdę mieć domyślnej implementacji, albo że popełniłeś błąd podczas projektowania interfejsu API.

  • Nie można odróżnić implementacji domyślnej od żadnej implementacji , przynajmniej bez rozwiązania tego problemu za pomocą specjalnych wartości zwracanych. Rozważ następujący przykład:

    protocol SomeParserDelegate {
        func validate(value: Any) -> Bool
    }

    Jeśli zapewnisz domyślną implementację, która po prostu zwraca true- na pierwszy rzut oka jest w porządku. Teraz rozważ następujący pseudo kod:

    final class SomeParser {
        func parse(data: Data) -> [Any] {
            if /* delegate.validate(value:) is not implemented */ {
                /* parse very fast without validating */
            } else {
                /* parse and validate every value */
            }
        }
    }

    Nie ma sposobu na wdrożenie takiej optymalizacji - nie możesz wiedzieć, czy Twój delegat wdraża metodę, czy nie.

    Chociaż istnieje wiele różnych sposobów rozwiązania tego problemu (przy użyciu opcjonalnych zamknięć, różnych obiektów delegowanych dla różnych operacji, aby wymienić tylko kilka), ten przykład pokazuje problem w jasny sposób.


2. Korzystanie @objc optional.

@objc protocol MyProtocol {
    @objc optional func doSomething()
}

class MyClass: NSObject, MyProtocol {
    /* no compile error */
}

Zalety

  • Domyślna implementacja nie jest wymagana. Po prostu deklarujesz opcjonalną metodę lub zmienną i jesteś gotowy do pracy.

Niedogodności

  • Ogranicza to znacznie możliwości twojego protokołu, wymagając od wszystkich zgodnych typów zgodności z Objective-C. Oznacza to, że tylko klasy, które dziedziczą, NSObjectmogą być zgodne z takim protokołem. Bez struktur, bez wyliczeń, bez powiązanych typów.

  • Zawsze musisz sprawdzić, czy opcjonalna metoda jest zaimplementowana , opcjonalnie wywołując ją lub sprawdzając, czy typ zgodny ją implementuje. Może to wprowadzać wiele bojlerów, jeśli często wywołujesz metody opcjonalne.

akashivskyy
źródło
17
Spójrz na ulepszony sposób opcjonalnych metod w szybkim użyciu rozszerzenia (poniżej)
Daniel Kanaan,
1
A w jaki sposób testuje się obsługę opcjonalnej metody protokołu w instancji? respondsToSelector?
devios1
3
Po prostu nie rób tego! Swift z jakiegoś powodu nie obsługuje opcjonalnej metody w protokołach.
fpg1503
2
Ta metoda nie obsługuje opcji w parametrach. Tzn. Nie możesz tego zrobićoptional func doSomething(param: Int?)
SoftDesigner,
4
Z pewnością ta odpowiedź jest zasadniczo błędna obecnie, po latach. Dzisiaj w Swift po prostu dodajesz rozszerzenie do domyślnej implementacji, jest to podstawowy aspekt Swift. (Jak pokazują wszystkie współczesne odpowiedzi poniżej). Błędem byłoby obecnie dodać flagę objc.
Fattie
394

W Swift 2 i nowszych można dodawać domyślne implementacje protokołu. To tworzy nowy sposób opcjonalnych metod w protokołach.

protocol MyProtocol {
    func doSomethingNonOptionalMethod()
    func doSomethingOptionalMethod()
}

extension MyProtocol {
    func doSomethingOptionalMethod(){ 
        // leaving this empty 
    }
}

To nie jest naprawdę fajny sposób na tworzenie opcjonalnych metod protokołów, ale daje możliwość użycia struktur w wywołaniach zwrotnych protokołu.

Napisałem tutaj małe streszczenie: https://www.avanderlee.com/swift-2-0/optional-protocol-methods/

Antoine
źródło
2
Jest to prawdopodobnie najczystszy sposób, aby to zrobić w Swift. Szkoda, że ​​nie działa przed Swift 2.0.
Entalpi
13
@MattQuiros Uważam, że w rzeczywistości musisz zadeklarować funkcję w definicji protokołu, w przeciwnym razie funkcja rozszerzenia no-op nie zostanie zastąpiona w twoich klasach zgodnych z protokołem.
Ian Pearce
3
@IanPearce jest poprawny i wydaje się, że jest to zgodne z projektem. W przemówieniu „Programowanie zorientowane na protokół” (408) w WWDC rozmawiają o metodach w głównym protokole, którymi są „punkty dostosowywania” oferowane typom zgodnym. Wymagany punkt dostosowania nie otrzymuje definicji w rozszerzeniu; opcjonalny punkt tak. Metody w protokole, które zasadniczo nie powinny być dostosowywane, są całkowicie deklarowane / definiowane w rozszerzeniu, aby uniemożliwić dostosowywanie typów zgodnych do dostosowywania, chyba że specjalnie wybierzesz opcję DynamicType, aby pokazać, że chcesz niestandardowej implementacji konformatora.
Matthias
4
@FranklinYu Możesz to zrobić, ale następnie zanieczyszczasz swój projekt API za pomocą „rzutów” tam, gdzie w rzeczywistości nie jest potrzebny. Bardziej podoba mi się pomysł „mikro protokołów”. Np. Każda metoda potwierdza jeden protokół, a następnie można sprawdzić: jeśli obiekt jest protokołem
Darko
3
@Antoine Myślę, że powinieneś upublicznić funkcję w rozszerzeniu, ponieważ funkcje w protokołach są z definicji publiczne. Twoje rozwiązanie nie będzie działać, gdy protokół będzie używany poza jego modułem.
Vadim Eisenberg
39

Ponieważ istnieją odpowiedzi na temat używania opcjonalnego modyfikatora i atrybutu @objc do definiowania opcjonalnego protokołu wymagań, dam przykład, jak używać rozszerzeń protokołu definiujących opcjonalny protokół.

Poniższy kod to Swift 3. *.

/// Protocol has empty default implementation of the following methods making them optional to implement:
/// `cancel()`
protocol Cancelable {

    /// default implementation is empty.
    func cancel()
}

extension Cancelable {

    func cancel() {}
}

class Plane: Cancelable {
  //Since cancel() have default implementation, that is optional to class Plane
}

let plane = Plane()
plane.cancel()
// Print out *United Airlines can't cancelable*

Należy zauważyć, że metody rozszerzenia protokołu nie mogą być wywoływane przez kod Objective-C, a co gorsza, zespół Swift go nie naprawi. https://bugs.swift.org/browse/SR-492

Eddie.Dou
źródło
1
Dobra robota! To powinna być poprawna odpowiedź, ponieważ rozwiązuje problem bez angażowania środowiska wykonawczego Objective-C.
Lukas,
naprawdę fajny!
tania_S,
z szybkiej dokumentacji - „Wymagania protokołu z domyślnymi implementacjami dostarczonymi przez rozszerzenia różnią się od opcjonalnych wymagań protokołu. Chociaż typy zgodne nie muszą przedstawiać własnej implementacji, wymagania z implementacjami domyślnymi można wywoływać bez opcjonalnego łączenia”.
Mec Os
34

Inne odpowiedzi dotyczące oznaczenia protokołu jako „@objc” nie działają, gdy używane są typy szybkie.

struct Info {
    var height: Int
    var weight: Int
} 

@objc protocol Health {
    func isInfoHealthy(info: Info) -> Bool
} 
//Error "Method cannot be marked @objc because the type of the parameter cannot be represented in Objective-C"

Aby zadeklarować opcjonalne protokoły, które działają dobrze w swift, zadeklaruj funkcje jako zmienne zamiast func.

protocol Health {
    var isInfoHealthy: (Info) -> (Bool)? { get set }
}

A następnie zaimplementuj protokół w następujący sposób

class Human: Health {
    var isInfoHealthy: (Info) -> (Bool)? = { info in
        if info.weight < 200 && info.height > 72 {
            return true
        }
        return false
    }
    //Or leave out the implementation and declare it as:  
    //var isInfoHealthy: (Info) -> (Bool)?
}

Następnie możesz użyć „?” aby sprawdzić, czy funkcja została zaimplementowana

func returnEntity() -> Health {
    return Human()
}

var anEntity: Health = returnEntity()

var isHealthy = anEntity.isInfoHealthy(Info(height: 75, weight: 150))? 
//"isHealthy" is true
Zag
źródło
3
Dzięki temu rozwiązaniu nie możesz uzyskać dostępu do siebie z żadnej z funkcji protokołu. W niektórych przypadkach może to powodować problemy!
George Green,
1
@GeorgeGreen Możesz uzyskać dostęp do siebie. Oznacz zmienną funkcyjną jako leniwą i użyj listy przechwytywania wewnątrz zamknięcia .
Zag
Tylko klasy, protokoły, metody i właściwości mogą używać @objc. Jeśli używasz parametru Enum w definicji metody protokołu @ objc, jesteś skazany.
khunshan
1
@khunshan, ta metoda nie wymaga oznaczania niczego @ objc, o czym mówisz?
Zag
jest to informacja na ten temat, że wyliczenia nie mogą być używane między swift a objc, które dla innych instrukcji można połączyć ze słowem kluczowym @objc.
khunshan,
34

Oto konkretny przykład ze schematem delegowania.

Skonfiguruj protokół:

@objc protocol MyProtocol:class
{
    func requiredMethod()
    optional func optionalMethod()
}

class MyClass: NSObject
{
    weak var delegate:MyProtocol?

    func callDelegate()
    {
        delegate?.requiredMethod()
        delegate?.optionalMethod?()
    }
}

Ustaw delegata na klasę i zaimplementuj protokół. Sprawdź, czy opcjonalna metoda nie musi być zaimplementowana.

class AnotherClass: NSObject, MyProtocol
{
    init()
    {
        super.init()

        let myInstance = MyClass()
        myInstance.delegate = self
    }

    func requiredMethod()
    {
    }
}

Jedną ważną rzeczą jest to, że opcjonalna metoda jest opcjonalna i wymaga „?” podczas dzwonienia. Wspomnij o drugim znaku zapytania.

delegate?.optionalMethod?()
Raphael
źródło
2
To powinna być poprawna odpowiedź. Prosty, czysty i objaśniający.
Echelon,
Zgadzam się, ta odpowiedź jest krótka, słodka i od razu do rzeczy. dzięki!
LuAndre,
31

W Swift 3.0

@objc protocol CounterDataSource {
    @objc optional func increment(forCount count: Int) -> Int
    @objc optional var fixedIncrement: Int { get }
}

Zaoszczędzi to Twój czas.

Gautam Sareriya
źródło
6
Jakieś źródło, dlaczego @objcjest teraz potrzebne wszystkim członkom?
DevAndArtist
@Gautam Sareriya: Co według Ciebie jest najlepsze w tym podejściu lub tworzeniu pustych metod rozszerzenia?
eonista
Próbowałem z twoimi metodami z requiredflagą, ale z błędami: requiredmogą być używane tylko w deklaracjach „init”.
Ben
27
  • Musisz dodać optionalsłowo kluczowe przed każdą metodą.
  • Pamiętaj jednak, że aby to zadziałało, twój protokół musi być oznaczony atrybutem @objc.
  • Oznacza to ponadto, że protokół ten miałby zastosowanie do klas, ale nie struktur.
Mehul Parmar
źródło
Drobna korekta (zbyt mała, by ją edytować!), Ale powinna być „opcjonalna”, a nie „@opcjonalna”
Ali Beadle
5
Wymaga to również oznaczenia klasy zaimplementowanej protokołu jako @objcnie tylko protokołu.
Johnathon Sullinger,
13

Podejście czysto szybkie z dziedziczeniem protokołu:

//Required methods
protocol MyProtocol {
    func foo()
}

//Optional methods
protocol MyExtendedProtocol: MyProtocol {
    func bar()
}

class MyClass {
    var delegate: MyProtocol
    func myMethod() {
        (delegate as? MyExtendedProtocol).bar()
    }
}
guenis
źródło
11

Aby zilustrować mechanikę odpowiedzi Antoine'a:

protocol SomeProtocol {
    func aMethod()
}

extension SomeProtocol {
    func aMethod() {
        print("extensionImplementation")
    }
}

class protocolImplementingObject: SomeProtocol {

}

class protocolImplementingMethodOverridingObject: SomeProtocol {
    func aMethod() {
        print("classImplementation")
    }
}

let noOverride = protocolImplementingObject()
let override = protocolImplementingMethodOverridingObject()

noOverride.aMethod() //prints "extensionImplementation"
override.aMethod() //prints "classImplementation"
eLillie
źródło
8

Myślę, że zanim zapytasz, jak możesz wdrożyć opcjonalną metodę protokołu, powinieneś zapytać, dlaczego powinieneś ją wdrożyć.

Jeśli myślimy o szybkich protokołach jako interfejsie w klasycznym programowaniu obiektowym, opcjonalne metody nie mają większego sensu, a być może lepszym rozwiązaniem byłoby stworzenie domyślnej implementacji lub rozdzielenie protokołu na zestaw protokołów (być może z pewnymi relacjami dziedziczenia między nimi) w celu przedstawienia możliwej kombinacji metod w protokole.

Więcej informacji można znaleźć na stronie https://useyourloaf.com/blog/swift-optional-protocol-methods/ , która daje doskonały przegląd tej kwestii.

emem
źródło
To. Jest to zgodne z zasadą „I” w zasadach tworzenia oprogramowania SOLID .
Eric
7

Nieco temat z pierwotnego pytania, ale stanowi rozwinięcie pomysłu Antoine'a i pomyślałem, że może komuś pomóc.

Możesz także ustawić właściwości obliczane jako opcjonalne dla struktur z rozszerzeniami protokołu.

Możesz ustawić właściwość protokołu jako opcjonalną

protocol SomeProtocol {
    var required: String { get }
    var optional: String? { get }
}

Zaimplementuj obojętną właściwość obliczoną w rozszerzeniu protokołu

extension SomeProtocol {
    var optional: String? { return nil }
}

A teraz możesz używać struktur, które mają lub nie mają zaimplementowanej opcjonalnej właściwości

struct ConformsWithoutOptional {
    let required: String
}

struct ConformsWithOptional {
    let required: String
    let optional: String?
}

Napisałem również, jak robić opcjonalne właściwości w protokołach Swift na moim blogu , który będę aktualizowany na wypadek, gdyby coś się zmieniło w wydaniach Swift 2.

matthewpalmer
źródło
5

Jak utworzyć opcjonalne i wymagane metody delegowania.

@objc protocol InterViewDelegate:class {

    @objc optional func optfunc()  //    This is optional
    func requiredfunc()//     This is required 

}
Saurabh Sharma
źródło
3

Oto bardzo prosty przykład TYLKO dla szybkich klas, a nie struktur i wyliczeń. Zauważ, że metoda protokołu jest opcjonalna, ma dwa poziomy opcjonalnego łączenia w grę. Również klasa przyjmująca protokół potrzebuje atrybutu @objc w swojej deklaracji.

@objc protocol CollectionOfDataDelegate{
   optional func indexDidChange(index: Int)
}


@objc class RootView: CollectionOfDataDelegate{

    var data = CollectionOfData()

   init(){
      data.delegate = self
      data.indexIsNow()
   }

  func indexDidChange(index: Int) {
      println("The index is currently: \(index)")
  }

}

class CollectionOfData{
    var index : Int?
    weak var delegate : CollectionOfDataDelegate?

   func indexIsNow(){
      index = 23
      delegate?.indexDidChange?(index!)
    }

 }
Błogosławieństwo Lopes
źródło
Czy możesz opisać nieco bardziej część „ dwa poziomy opcjonalnej gry ”, a mianowicie delegate?.indexDidChange?(index!):?
Unheilig,
2
Gdybyśmy napisali protokół, aby mieć nie opcjonalną metodę taką jak ta: protocol CollectionOfDataDelegate{ func indexDidChange(index: Int) } wtedy wywołalibyśmy go bez znaku zapytania: delegate?.indexDidChange(index!) Kiedy ustawisz opcjonalne wymaganie dla metody w protokole, Typ, który będzie z nią zgodny, NIE może wdrożyć tej metody , więc ?służy do sprawdzania implementacji, a jeśli nie ma, program się nie zawiesi. @Unheilig
Blessing Lopes
weak var delegate : CollectionOfDataDelegate?(zapewnić słabe referencje?)
Alfie Hanssen
@BlessingLopes Czy możesz dodać wyjaśnienie delegate?zastosowania do swojej odpowiedzi? Ta informacja powinna naprawdę należeć do nich w przyszłości. Chcę to zagłosować, ale ta informacja powinna naprawdę znaleźć się w odpowiedzi.
Johnathon Sullinger,
3

jeśli chcesz to zrobić czysto szybko, najlepszym sposobem jest zapewnienie domyślnej implementacji, szczególnie jeśli zwrócisz typ Swift, na przykład struct z typami Swift

przykład:

struct magicDatas {
    var damagePoints : Int?
    var manaPoints : Int?
}

protocol magicCastDelegate {
    func castFire() -> magicDatas
    func castIce() -> magicDatas
}

extension magicCastDelegate {
    func castFire() -> magicDatas {
        return magicDatas()
    }

    func castIce() -> magicDatas {
        return magicDatas()
    }
}

następnie możesz wdrożyć protokół bez definiowania każdego func

p1ckl3
źródło
2

Istnieją dwa sposoby utworzenia opcjonalnej metody w szybkim protokole.

1 - Pierwszą opcją jest oznaczenie protokołu za pomocą atrybutu @objc. Chociaż oznacza to, że może być przyjęty tylko przez klasy, oznacza to, że oznaczysz poszczególne metody jako opcjonalne:

@objc protocol MyProtocol {
    @objc optional func optionalMethod()
}

2 - Szybszy sposób: Ta opcja jest lepsza. Napisz domyślne implementacje opcjonalnych metod, które nic nie robią, takie jak to.

protocol MyProtocol {
    func optionalMethod()
    func notOptionalMethod()
}

extension MyProtocol {

    func optionalMethod() {
        //this is a empty implementation to allow this method to be optional
    }
}

Swift ma funkcję o nazwie rozszerzenie, która pozwala nam zapewnić domyślną implementację tych metod, które chcemy być opcjonalne.

Himanshu
źródło
0

Jedną z opcji jest przechowywanie ich jako opcjonalnych zmiennych funkcji:

struct MyAwesomeStruct {
    var myWonderfulFunction : Optional<(Int) -> Int> = nil
}

let squareCalculator =
    MyAwesomeStruct(myWonderfulFunction: { input in return input * input })
let thisShouldBeFour = squareCalculator.myWonderfulFunction!(2)
FrankByte.com
źródło
-1

Zdefiniuj funkcję w protokole i utwórz rozszerzenie dla tego protokołu, a następnie utwórz pustą implementację dla funkcji, której chcesz używać jako opcjonalnej.

Rishu Gupta
źródło
-2

Aby Optional Protocolszybko zdefiniować , należy użyć @objcsłowa kluczowego przed Protocoldeklaracją i attribute/ methoddeklaracją wewnątrz tego protokołu. Poniżej znajduje się próbka opcjonalnej właściwości protokołu.

@objc protocol Protocol {

  @objc optional var name:String?

}

class MyClass: Protocol {

   // No error

}
Ankur Gupta
źródło
2
Chociaż może to odpowiedzieć na pytanie, lepiej dodać opis, w jaki sposób ta odpowiedź może pomóc w rozwiązaniu problemu. Proszę przeczytać Jak napisać dobrą odpowiedź, aby dowiedzieć się więcej.
Roshana Pitigala
-23

Umieść @optionalprzed metodami lub właściwościami.

shucao
źródło
Błąd kompilatora: atrybut „opcjonalny” można zastosować tylko do członków protokołu @objc.
Selvin
3
@optionalnie jest nawet prawidłowym słowem kluczowym. Jest optionali musisz zadeklarować klasę i protokół z tym @objcatrybutem.
Johnathon Sullinger,