Swift Language
protocollen
Zoeken…
Invoering
Protocollen zijn een manier om aan te geven hoe een object moet worden gebruikt. Ze beschrijven een set eigenschappen en methoden die een klasse, structuur of opsomming moet bieden, hoewel protocollen geen beperkingen opleggen aan de implementatie.
Opmerkingen
Een Swift-protocol is een verzameling vereisten die conforme typen moeten implementeren. Het protocol kan vervolgens worden gebruikt op de meeste plaatsen waar een type wordt verwacht, bijvoorbeeld arrays en generieke vereisten.
Protocolleden delen altijd dezelfde toegangskwalificatie als het hele protocol en kunnen niet afzonderlijk worden opgegeven. Hoewel een protocol de toegang kan beperken met de vereisten van getter of setter, zoals in bovenstaande voorbeelden.
Zie De snelle programmeertaal voor meer informatie over protocollen.
Objective-C-protocollen zijn vergelijkbaar met Swift-protocollen.
Protocollen zijn ook vergelijkbaar met Java-interfaces .
Basisprincipes van het protocol
Over protocollen
Een protocol specificeert initializers, eigenschappen, functies, subscripts en bijbehorende types die vereist zijn voor een Swift-objecttype (klasse, struct of enum) conform het protocol. In sommige talen worden vergelijkbare ideeën voor vereistenpecificaties van volgende objecten 'interfaces' genoemd.
Een verklaard en gedefinieerd protocol is op zichzelf een type met een handtekening van de gestelde eisen, enigszins vergelijkbaar met de manier waarop Swift-functies een type zijn op basis van hun handtekening van parameters en retouren.
Swift-protocolspecificaties kunnen optioneel, expliciet vereist en / of standaardimplementaties zijn via een faciliteit die bekend staat als protocolextensies. Een Swift-objecttype (klasse, struct of enum) die zich wenst te conformeren aan een protocol dat is ingevuld met extensies voor al zijn gespecificeerde vereisten, behoeft alleen zijn wens te conformeren om volledig conform te zijn. De standaard implementatiefaciliteit van Protocol Extensies kan voldoende zijn om te voldoen aan alle verplichtingen om aan een Protocol te voldoen.
Protocollen kunnen worden overgenomen door andere protocollen. Dit betekent, in combinatie met protocoluitbreidingen, dat protocollen als een belangrijk kenmerk van Swift kunnen en moeten worden beschouwd.
Protocollen en uitbreidingen zijn belangrijk om de bredere doelstellingen en benaderingen van Swift voor programmaflexibiliteit en ontwikkelingsprocessen te realiseren. Het primaire verklaarde doel van Swift's protocol- en uitbreidingsmogelijkheden is het faciliteren van compositorisch ontwerp in programma-architectuur en ontwikkeling. Dit wordt Protocol Oriented Programming genoemd. Knapperige oldtimers beschouwen deze superieur als een focus op OOP-ontwerp.
Protocollen definiëren interfaces die kunnen worden geïmplementeerd door elke struct , klasse of enum :
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
}
Eigenschappen die in protocollen zijn gedefinieerd, moeten worden geannoteerd als { get }
of { get set }
. { get }
betekent dat de eigenschap gettable moet zijn en daarom als elke soort eigenschap kan worden geïmplementeerd. { get set }
betekent dat de eigenschap zowel instelbaar als gettable moet zijn.
Een struct, klasse of opsomming kan voldoen aan een protocol:
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
}
Een protocol kan ook een standaardimplementatie definiëren voor elk van de vereisten via een extensie :
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
}
}
Een protocol kan als een type worden gebruikt , op voorwaarde dat het geen associatedtype
typevereisten heeft :
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]
U kunt ook een abstract type definiëren dat voldoet aan meerdere protocollen:
Met Swift 3 of hoger wordt dit gedaan door de lijst met protocollen te scheiden met een en-teken ( &
):
func doStuff(object: MyProtocol & AnotherProtocol) {
// ...
}
let items : [MyProtocol & AnotherProtocol] = [MyStruct(), MyClass(), MyEnum.caseA]
Oudere versies hebben een syntaxisprotocol protocol<...>
waarbij de protocollen een door komma's gescheiden lijst zijn tussen de punthaken <>
.
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]
Bestaande typen kunnen worden uitgebreid om te voldoen aan een protocol:
extension String : MyProtocol {
// Implement any requirements which String doesn't already satisfy
}
Bijbehorende typevereisten
Protocollen kunnen bijbehorende typevereisten definiëren met behulp van het associatedtype
trefwoord:
protocol Container {
associatedtype Element
var count: Int { get }
subscript(index: Int) -> Element { get set }
}
Protocollen met bijbehorende typevereisten kunnen alleen worden gebruikt als generieke beperkingen :
// 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 }
Een type dat overeenkomt met het protocol kan een aan associatedtype
vereiste impliciet, door een bepaald type indien het protocol verwacht dat de associatedtype
worden weergegeven:
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")
(Merk op dat voor de duidelijkheid van dit voorbeeld het generieke type tijdelijke aanduiding de naam T
- een geschiktere naam zou Element
, dat het associatedtype Element
het protocol zou associatedtype Element
. De compiler concludeert nog steeds dat het generieke Element
tijdelijke aanduiding wordt gebruikt om aan het associatedtype Element
te voldoen associatedtype Element
.)
Aan een associatedtype
kan ook expliciet worden voldaan door het gebruik van een typealias
:
struct ContainerOfOne<T>: Container {
typealias Element = T
subscript(index: Int) -> Element { ... }
// ...
}
Hetzelfde geldt voor extensies:
// 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))
}
}
}
}
Als het conformerende type al aan de vereiste voldoet, is geen implementatie vereist:
extension Array: Container {} // Array satisfies all requirements, including Element
Patroon delegeren
Een gemachtigde is een gebruikelijk ontwerppatroon dat wordt gebruikt in Cocoa en CocoaTouch-frameworks, waarbij de ene klasse de verantwoordelijkheid voor het implementeren van bepaalde functionaliteit aan de andere delegeert. Dit volgt een principe van scheiding van punten van zorg, waarbij de framework-klasse generieke functionaliteit implementeert, terwijl een afzonderlijke gedelegeerde instantie de specifieke use case implementeert.
Een andere manier om het deelnemerspatroon te bekijken is in termen van objectcommunicatie. Objects
vaak met elkaar praten en daarvoor moet een object voldoen aan een protocol
om een afgevaardigde van een ander object te worden. Zodra deze installatie is voltooid, praat het andere object terug naar zijn afgevaardigden wanneer er interessante dingen gebeuren.
Een weergave in een gebruikersinterface om een lijst met gegevens weer te geven, moet bijvoorbeeld alleen verantwoordelijk zijn voor de logica van hoe gegevens worden weergegeven, niet voor de beslissing welke gegevens moeten worden weergegeven.
Laten we een meer concreet voorbeeld bekijken. als je twee klassen hebt, een ouder en een kind:
class Parent { }
class Child { }
En u wilt de ouder op de hoogte stellen van een wijziging van het kind.
In Swift worden afgevaardigden geïmplementeerd met behulp van een protocol
en daarom verklaren we een protocol
dat de delegate
zal implementeren. Hier is de gemachtigde het parent
object.
protocol ChildDelegate: class {
func childDidSomething()
}
Het kind moet een eigenschap aangeven om de verwijzing naar de gemachtigde op te slaan:
class Child {
weak var delegate: ChildDelegate?
}
Merk op dat de variabele delegate
een optioneel is en dat het protocol ChildDelegate
is gemarkeerd om alleen per ChildDelegate
te worden geïmplementeerd (zonder dit kan de delegate
variabele niet worden gedeclareerd als een weak
referentie om een ChildDelegate
te voorkomen. Dit betekent dat als de delegate
variabele niet langer is waarnaar elders wordt verwezen, wordt deze vrijgegeven). Dit betekent dat de bovenliggende klasse de gedelegeerde alleen registreert wanneer deze nodig en beschikbaar is.
Ook om onze gemachtigde als weak
te markeren, moeten we ons ChildDelegate-protocol beperken tot referentietypes door class
sleutelwoorden toe te voegen in de protocoldeclaratie.
Wanneer het kind in dit voorbeeld iets doet en zijn ouder op de hoogte moet stellen, belt het kind:
delegate?.childDidSomething()
Als de gemachtigde is gedefinieerd, krijgt de gemachtigde een melding dat het kind iets heeft gedaan.
De bovenliggende klasse moet het ChildDelegate
protocol uitbreiden om op zijn acties te kunnen reageren. Dit kan rechtstreeks op de bovenliggende klasse worden gedaan:
class Parent: ChildDelegate {
...
func childDidSomething() {
print("Yay!")
}
}
Of gebruik een extensie:
extension Parent: ChildDelegate {
func childDidSomething() {
print("Yay!")
}
}
De ouder moet het kind ook vertellen dat het de afgevaardigde van het kind is:
// In the parent
let child = Child()
child.delegate = self
Standaard staat een Swift- protocol
niet toe dat een optionele functie wordt geïmplementeerd. Deze kunnen alleen worden opgegeven als uw protocol is gemarkeerd met het kenmerk @objc
en de optional
modifier.
UITableView
implementeert bijvoorbeeld het generieke gedrag van een tabelweergave in iOS, maar de gebruiker moet twee gedelegeerde klassen implementeren, UITableViewDelegate
en UITableViewDataSource
die implementeren hoe de specifieke cellen eruit zien en zich gedragen.
@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) ... }
U kunt dit protocol implementeren door uw klassendefinitie te wijzigen, bijvoorbeeld:
class MyViewController : UIViewController, UITableViewDelegate
Alle methoden die niet als optional
gemarkeerd in de protocoldefinitie ( UITableViewDelegate
in dit geval) moeten worden geïmplementeerd.
Protocoluitbreiding voor een specifieke conformerende klasse
U kunt de standaardprotocolimplementatie voor een specifieke klasse schrijven.
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"
Het RawRepresentable-protocol gebruiken (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")
}
Deze structuur kan elders worden uitgebreid om cases toe te voegen
extension NotificationName {
static let documentationLaunched = NotificationNames(rawValue: "DocumentationLaunchedNotification")
}
En een interface kan rond elk type RawRepresentable of specifiek uw enum struct ontwerpen
func post(notification notification: NotificationName) -> Void {
// use notification.rawValue
}
Op de oproepsite kunt u puntsyntaxis steno gebruiken voor de typenveilige notificatienaam
post(notification: .dataFinished)
Gebruik van de generieke RawRepresentable-functie
// 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
}
Alleen-klasse protocollen
Een protocol kan specificeren dat alleen een klasse het kan implementeren door het sleutelwoord class
in zijn overnamelijst te gebruiken. Dit trefwoord moet vóór alle andere overgenomen protocollen in deze lijst verschijnen.
protocol ClassOnlyProtocol: class, SomeOtherProtocol {
// Protocol requirements
}
Als een niet-klasse type ClassOnlyProtocol
probeert te implementeren, wordt een compilerfout gegenereerd.
struct MyStruct: ClassOnlyProtocol {
// error: Non-class type 'MyStruct' cannot conform to class protocol 'ClassOnlyProtocol'
}
Andere protocollen kunnen erven van het ClassOnlyProtocol
, maar ze hebben dezelfde vereiste voor alleen klassen.
protocol MyProtocol: ClassOnlyProtocol {
// ClassOnlyProtocol Requirements
// MyProtocol Requirements
}
class MySecondClass: MyProtocol {
// ClassOnlyProtocol Requirements
// MyProtocol Requirements
}
Referentie semantiek van alleen-klasse protocollen
Het gebruik van een alleen-klasse protocol maakt referentie-semantiek mogelijk wanneer het conformerende type onbekend is.
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"
}
In dit voorbeeld, omdat Foo
een protocol is dat alleen voor klassen is, is de toewijzing aan bar
geldig omdat de compiler weet dat foo
een klasse-type is en daarom referentie-semantiek heeft.
Als Foo
geen protocol voor alleen klassen was, zou een compilerfout worden verkregen - omdat het conformerende type een waardetype zou kunnen zijn, waarvoor een var
annotatie nodig zou zijn om veranderlijk te zijn.
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
}
Zwakke variabelen van het protocoltype
Wanneer de weak
modifier wordt toegepast op een variabele van het protocoltype, moet dat protocoltype alleen klasse zijn, omdat weak
alleen kan worden toegepast op referentietypen.
weak var weakReference : ClassOnlyProtocol?
Hashable-protocol implementeren
Typen die worden gebruikt in Sets
en Dictionaries(key)
moeten voldoen aan het Hashable
protocol dat Equatable
van het Equatable
protocol.
Aangepast type dat voldoet aan Hashable
protocol moet worden geïmplementeerd
- Een berekende eigenschap
hashValue
- Definieer een van de gelijkheidsexploitanten ie
==
of!=
.
Het volgende voorbeeld implementeert Hashable
protocol voor een aangepaste struct
:
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))
Opmerking : het is niet nodig dat verschillende waarden in het aangepaste type verschillende hash-waarden hebben, botsingen zijn acceptabel. Als hash-waarden gelijk zijn, wordt de operator voor gelijkheid gebruikt om de echte gelijkheid te bepalen.