Szukaj…


Wprowadzenie

Protokoły to sposób określania sposobu używania obiektu. Opisują zestaw właściwości i metod, które powinna zapewnić klasa, struktura lub wyliczenie, chociaż protokoły nie nakładają żadnych ograniczeń na implementację.

Uwagi

Protokół Swift to zbiór wymagań, które muszą spełniać zgodne typy. Protokół może być następnie używany w większości miejsc, w których oczekiwany jest typ, na przykład tablice i wymagania ogólne.

Członkowie protokołu zawsze dzielą ten sam kwalifikator dostępu co cały protokół i nie można ich określać osobno. Chociaż protokół może ograniczać dostęp z wymaganiami gettera lub settera, jak w powyższych przykładach.

Aby uzyskać więcej informacji na temat protokołów, zobacz Swift Programming Language .

Protokoły Objective-C są podobne do protokołów Swift.

Protokoły są również porównywalne z interfejsami Java .

Podstawy protokołu

Informacje o protokołach

Protokół określa inicjatory, właściwości, funkcje, indeksy dolne i powiązane typy wymagane od typu obiektu Swift (klasy, struktury lub wyliczenia) zgodnego z protokołem. W niektórych językach podobne pomysły na specyfikację wymagań dla kolejnych obiektów są znane jako „interfejsy”.

Zadeklarowany i zdefiniowany protokół sam w sobie jest podpisem jego określonych wymagań, nieco podobny do sposobu, w jaki Swift Functions są typem na podstawie podpisu parametrów i zwrotów.

Specyfikacje protokołu Swift mogą być opcjonalne, wyraźnie wymagane i / lub mieć domyślne implementacje za pośrednictwem narzędzia znanego jako Rozszerzenia protokołu. Szybki typ obiektu (klasa, struktura lub wyliczenie), który chce być zgodny z protokołem opracowanym z rozszerzeniami dla wszystkich określonych wymagań, musi jedynie wyrazić chęć dostosowania się do pełnej zgodności. Domyślna funkcja implementacji rozszerzeń protokołu może wystarczyć do spełnienia wszystkich obowiązków związanych z przestrzeganiem protokołu.

Protokoły mogą być dziedziczone przez inne protokoły. To, w połączeniu z rozszerzeniami protokołu, oznacza, że protokoły mogą i powinny być uważane za znaczącą cechę Swift.

Protokoły i rozszerzenia są ważne dla realizacji szerszych celów i podejść Swift do elastyczności projektowania programów i procesów rozwojowych. Głównym deklarowanym celem Swift's Protocol and Extension jest ułatwienie projektowania kompozycji w architekturze i rozwoju programu. Nazywa się to programowaniem zorientowanym na protokół. Stare, chrupiące zegary uważają to za lepsze niż skupienie się na projektowaniu OOP.

Protokoły definiują interfejsy, które mogą być implementowane przez dowolną strukturę , klasę lub wyliczenie :

protocol MyProtocol {
    init(value: Int)                      // required initializer
    func doSomething() -> Bool            // instance method
    var message: String { get }           // instance read-only property
    var value: Int { get set }            // read-write instance property
    subscript(index: Int) -> Int { get }  // instance subscript
    static func instructions() -> String  // static method
    static var max: Int { get }           // static read-only property
    static var total: Int { get set }     // read-write static property
}

Właściwości zdefiniowane w protokołach muszą być opatrzone adnotacjami jako { get } lub { get set } . { get } oznacza, że właściwość musi być możliwa do uzyskania, a zatem może być zaimplementowana jako dowolna właściwość. { get set } oznacza, że właściwość musi być możliwa do ustalenia, jak również gettable.

Struktury, klasy lub wyliczenia mogą być zgodne z protokołem:

struct MyStruct : MyProtocol {
    // Implement the protocol's requirements here
}
class MyClass : MyProtocol {
    // Implement the protocol's requirements here
}
enum MyEnum : MyProtocol {
    case caseA, caseB, caseC
    // Implement the protocol's requirements here
}

Protokół może również zdefiniować domyślną implementację dowolnego z jego wymagań poprzez rozszerzenie :

extension MyProtocol {
    
    // default implementation of doSomething() -> Bool
    // conforming types will use this implementation if they don't define their own
    func doSomething() -> Bool {
        print("do something!")
        return true
    }
}

Protokół może być używany jako typ , pod warunkiem, że nie ma associatedtype wymagań typu :

func doStuff(object: MyProtocol) {
    // All of MyProtocol's requirements are available on the object
    print(object.message)
    print(object.doSomething())
}

let items : [MyProtocol] = [MyStruct(), MyClass(), MyEnum.caseA]

Możesz także zdefiniować typ abstrakcyjny, który jest zgodny z wieloma protokołami:

3.0

W przypadku Swift 3 lub nowszego można to zrobić, oddzielając listę protokołów znakiem ampersand ( & ):

func doStuff(object: MyProtocol & AnotherProtocol) {
    // ...
}

let items : [MyProtocol & AnotherProtocol] = [MyStruct(), MyClass(), MyEnum.caseA]
3.0

Starsze wersje mają protocol<...> składni protocol<...> gdzie protokoły są listą oddzieloną przecinkami między nawiasami kątowymi <> .

protocol AnotherProtocol {
    func doSomethingElse()
}

func doStuff(object: protocol<MyProtocol, AnotherProtocol>) {
    
    // All of MyProtocol & AnotherProtocol's requirements are available on the object
    print(object.message)
    object.doSomethingElse()
}

// MyStruct, MyClass & MyEnum must now conform to both MyProtocol & AnotherProtocol
let items : [protocol<MyProtocol, AnotherProtocol>] = [MyStruct(), MyClass(), MyEnum.caseA]

Istniejące typy można rozszerzyć, aby były zgodne z protokołem:

extension String : MyProtocol {
    // Implement any requirements which String doesn't already satisfy
}

Powiązane wymagania dotyczące typu

Protokoły mogą określić związane z tym wymagania wpisywać za pomocą associatedtype słowa kluczowego:

protocol Container {
    associatedtype Element
    var count: Int { get }
    subscript(index: Int) -> Element { get set }
}

Protokoły z powiązanymi wymaganiami dotyczącymi typów mogą być używane tylko jako ogólne ograniczenia :

// These are NOT allowed, because Container has associated type requirements:
func displayValues(container: Container) { ... }
class MyClass { let container: Container }
// > error: protocol 'Container' can only be used as a generic constraint
// > because it has Self or associated type requirements

// These are allowed:
func displayValues<T: Container>(container: T) { ... }
class MyClass<T: Container> { let container: T }

Typ zgodny z protokołem może pośrednio spełniać wymaganie typu associatedtype , podając dany typ, w którym protokół oczekuje pojawienia się typu associatedtype :

struct ContainerOfOne<T>: Container {
    let count = 1          // satisfy the count requirement
    var value: T
    
    // satisfy the subscript associatedtype requirement implicitly,
    // by defining the subscript assignment/return type as T
    // therefore Swift will infer that T == Element
    subscript(index: Int) -> T {
        get {
            precondition(index == 0)
            return value
        }
        set {
            precondition(index == 0)
            value = newValue
        }
    }
}

let container = ContainerOfOne(value: "Hello")

(Należy zwrócić uwagę, że w celu zwiększenia przejrzystości tego przykładu ogólny typ symbolu zastępczego nosi nazwę T - bardziej odpowiednią nazwą byłby Element , który przesłaniałby associatedtype Element z protokołem. Kompilator nadal wywnioskuje, że ogólny Element zastępczy jest używany do spełnienia associatedtype Element Wymóg associatedtype Element .)

associatedtype typealias może być również wyraźnie spełniony poprzez użycie typealias :

struct ContainerOfOne<T>: Container {

    typealias Element = T
    subscript(index: Int) -> Element { ... }

    // ...
}

To samo dotyczy rozszerzeń:

// Expose an 8-bit integer as a collection of boolean values (one for each bit).
extension UInt8: Container {

    // as noted above, this typealias can be inferred
    typealias Element = Bool

    var count: Int { return 8 }
    subscript(index: Int) -> Bool {
        get {
            precondition(0 <= index && index < 8)
            return self & 1 << UInt8(index) != 0
        }
        set {
            precondition(0 <= index && index < 8)
            if newValue {
                self |= 1 << UInt8(index)
            } else {
                self &= ~(1 << UInt8(index))
            }
        }
    }
}

Jeśli zgodny typ już spełnia wymagania, nie jest wymagana implementacja:

extension Array: Container {}  // Array satisfies all requirements, including Element

Deleguj wzór

Delegat jest powszechnym wzorcem projektowym stosowanym w ramach Cocoa i CocoaTouch, gdzie jedna klasa przekazuje odpowiedzialność za wdrożenie niektórych funkcji na inną. Jest to zgodne z zasadą rozdzielania problemów, w której klasa ramowa implementuje ogólną funkcjonalność, a osobna instancja delegowana implementuje konkretny przypadek użycia.

Innym sposobem na sprawdzenie wzoru delegowania jest komunikacja obiektowa. Objects często muszą ze sobą rozmawiać i aby to zrobić, obiekt musi być zgodny z protocol , aby zostać delegatem innego obiektu. Po zakończeniu tej konfiguracji drugi obiekt mówi do swoich delegatów, gdy dzieje się coś ciekawego.

Na przykład widok w interfejsie użytkownika, aby wyświetlić listę danych, powinien odpowiadać tylko za logikę wyświetlania danych, a nie za decydowanie, które dane powinny być wyświetlane.

Zanurzmy się w bardziej konkretny przykład. jeśli masz dwie klasy, rodzic i dziecko:

class Parent { }
class Child { }

I chcesz powiadomić rodzica o zmianie od dziecka.

W Swift delegaci są implementowani za pomocą deklaracji protocol dlatego zadeklarujemy protocol który delegate wdroży. Tutaj delegat jest obiektem parent .

protocol ChildDelegate: class {
    func childDidSomething()
}

Dziecko musi zadeklarować właściwość, aby zapisać odwołanie do delegata:

class Child {
    weak var delegate: ChildDelegate?
}

Zwróć uwagę, że zmienna delegate jest opcjonalna, a protokół ChildDelegate jest oznaczony do zaimplementowania tylko według typu klasy (bez tego zmienna delegate nie może być zadeklarowana jako weak odwołanie unikające jakiegokolwiek cyklu przechowywania. Oznacza to, że jeśli zmienna delegate nie jest już dłużej gdziekolwiek indziej, zostanie wydany). Dzieje się tak, dlatego klasa nadrzędna rejestruje delegata tylko wtedy, gdy jest on potrzebny i dostępny.

Również w celu oznaczenia naszego delegata jako weak musimy ograniczyć nasz protokół ChildDelegate do typów referencyjnych poprzez dodanie słowa kluczowego class w deklaracji protokołu.

W tym przykładzie, gdy dziecko zrobi coś i będzie musiało powiadomić swojego rodzica, zadzwoni:

delegate?.childDidSomething()

Jeśli delegat został zdefiniowany, zostanie powiadomiony, że dziecko coś zrobiło.

Klasa nadrzędna będzie musiała rozszerzyć protokół ChildDelegate , aby móc reagować na swoje działania. Można to zrobić bezpośrednio w klasie nadrzędnej:

class Parent: ChildDelegate {
    ...

    func childDidSomething() {
        print("Yay!")
    }
}

Lub używając rozszerzenia:

extension Parent: ChildDelegate {
    func childDidSomething() {
        print("Yay!")
    }
}

Rodzic musi również powiedzieć dziecku, że jest delegatem dziecka:

// In the parent
let child = Child()
child.delegate = self

Domyślnie protocol Swift nie pozwala na implementację funkcji opcjonalnej. Można je określić tylko wtedy, gdy protokół jest oznaczony atrybutem @objc i optional modyfikatorem.

Na przykład UITableView implementuje ogólne zachowanie widoku tabeli w iOS, ale użytkownik musi zaimplementować dwie klasy delegowane o UITableViewDelegate i UITableViewDataSource które implementują wygląd i zachowanie określonych komórek.

@objc public protocol UITableViewDelegate : NSObjectProtocol, UIScrollViewDelegate {
        
        // Display customization
        optional public func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath)
        optional public func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int)
        optional public func tableView(tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int)
        optional public func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath)
        ...
}

Możesz wdrożyć ten protokół, zmieniając definicję klasy, na przykład:

class MyViewController : UIViewController, UITableViewDelegate

Wszystkie metody, które nie są oznaczone jako optional w definicji protokołu (w tym przypadku UITableViewDelegate ), muszą zostać zaimplementowane.

Rozszerzenie protokołu dla określonej klasy zgodnej

Możesz napisać domyślną implementację protokołu dla określonej klasy.

protocol MyProtocol {
    func doSomething()
}

extension MyProtocol where Self: UIViewController {
    func doSomething() {
        print("UIViewController default protocol implementation")
    }
}

class MyViewController: UIViewController, MyProtocol { }

let vc = MyViewController()
vc.doSomething() // Prints "UIViewController default protocol implementation"

Korzystanie z protokołu RawRepresentable (Extensible Enum)

// RawRepresentable has an associatedType RawValue.
// For this struct, we will make the compiler infer the type
// by implementing the rawValue variable with a type of String
//
// Compiler infers RawValue = String without needing typealias
//
struct NotificationName: RawRepresentable {
    let rawValue: String

    static let dataFinished = NotificationNames(rawValue: "DataFinishedNotification")
}

Strukturę tę można rozszerzyć w innym miejscu, aby dodać przypadki

extension NotificationName {
    static let documentationLaunched = NotificationNames(rawValue: "DocumentationLaunchedNotification")
}

Interfejs może projektować wokół dowolnego typu RawRepresentable lub konkretnie struktury enum

func post(notification notification: NotificationName) -> Void {
    // use notification.rawValue
}

W witrynie połączeń możesz użyć skróconej składni kropkowej dla bezpiecznego typu NotificationName

post(notification: .dataFinished)

Korzystanie z ogólnej funkcji RawRepresentable

// RawRepresentable has an associate type, so the 
// method that wants to accept any type conforming to
// RawRepresentable needs to be generic
func observe<T: RawRepresentable>(object: T) -> Void {
    // object.rawValue
}

Protokoły tylko dla klasy

Protokół może określać, że tylko klasa może go zaimplementować za pomocą słowa kluczowego class na liście dziedziczenia. To słowo kluczowe musi pojawić się przed innymi odziedziczonymi protokołami z tej listy.

protocol ClassOnlyProtocol: class, SomeOtherProtocol {
    // Protocol requirements 
}

Jeśli typ ClassOnlyProtocol próbuje zaimplementować ClassOnlyProtocol , zostanie wygenerowany błąd kompilatora.

struct MyStruct: ClassOnlyProtocol { 
    // error: Non-class type 'MyStruct' cannot conform to class protocol 'ClassOnlyProtocol'  
}

Inne protokoły mogą dziedziczyć po ClassOnlyProtocol , ale będą miały takie same wymagania tylko dla klasy.

protocol MyProtocol: ClassOnlyProtocol {
    // ClassOnlyProtocol Requirements
    // MyProtocol Requirements
}

class MySecondClass: MyProtocol {
    // ClassOnlyProtocol Requirements
    // MyProtocol Requirements
}

Odwołaj się do semantyki protokołów tylko klasowych

Użycie protokołu tylko dla klasy pozwala na semantykę odniesienia, gdy typ zgodny jest nieznany.

protocol Foo : class {
    var bar : String { get set }
}

func takesAFoo(foo:Foo) {

    // this assignment requires reference semantics,
    // as foo is a let constant in this scope.
    foo.bar = "new value"
}

W tym przykładzie, ponieważ Foo jest protokołem tylko dla klasy, przypisanie do bar jest prawidłowe, ponieważ kompilator wie, że foo jest typem klasy, a zatem ma semantykę odniesienia.

Gdyby Foo nie był protokołem wyłącznie klasowym, zostałby wygenerowany błąd kompilatora - ponieważ typ zgodny mógłby być typem wartości , który wymagałby adnotacji var , aby można go było modyfikować.

protocol Foo {
    var bar : String { get set }
}

func takesAFoo(foo:Foo) {
    foo.bar = "new value" // error: Cannot assign to property: 'foo' is a 'let' constant
}

func takesAFoo(foo:Foo) {
    var foo = foo // mutable copy of foo
    foo.bar = "new value" // no error – satisfies both reference and value semantics
}

Słabe zmienne typu protokołu

Podczas stosowania weak modyfikatora do zmiennej typu protokołu, ten typ protokołu musi być tylko dla klasy, ponieważ weak można zastosować tylko do typów referencyjnych.

weak var weakReference : ClassOnlyProtocol?

Implementowanie protokołu Hashable

Rodzaje stosowanych w Sets i Dictionaries(key) musi być zgodne z Hashable protokołu która dziedziczy z Equatable protokołu.

Należy zaimplementować niestandardowy typ zgodny z protokołem Hashable

  • Obliczona właściwość hashValue
  • Zdefiniuj jednego z operatorów równości tj. == lub != .

Poniższy przykład implementuje protokół Hashable dla struct niestandardowej:

struct Cell {
    var row: Int
    var col: Int
    
    init(_ row: Int, _ col: Int) {
        self.row = row
        self.col = col
    }
}

extension Cell: Hashable {
    
    // Satisfy Hashable requirement
    var hashValue: Int {
        get {
            return row.hashValue^col.hashValue
        }
    }

    // Satisfy Equatable requirement
    static func ==(lhs: Cell, rhs: Cell) -> Bool {
        return lhs.col == rhs.col && lhs.row == rhs.row
    }
    
}

// Now we can make Cell as key of dictonary
var dict = [Cell : String]()

dict[Cell(0, 0)] = "0, 0"
dict[Cell(1, 0)] = "1, 0"
dict[Cell(0, 1)] = "0, 1"

// Also we can create Set of Cells
var set = Set<Cell>()

set.insert(Cell(0, 0))
set.insert(Cell(1, 0))

Uwaga : Nie jest konieczne, aby różne wartości w typie niestandardowym miały różne wartości skrótu, kolizje są dopuszczalne. Jeśli wartości skrótu są równe, operator równości zostanie użyty do ustalenia rzeczywistej równości.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow