Swift Language
Protokoły
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:
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]
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.