Swift Language
protokoll
Sök…
Introduktion
Protokoll är ett sätt att specificera hur man använder ett objekt. De beskriver en uppsättning egenskaper och metoder som en klass, struktur eller enum bör tillhandahålla, även om protokoll inte innehåller några begränsningar för genomförandet.
Anmärkningar
Ett Swift-protokoll är en samling krav som överensstämmande typer måste implementera. Protokollet kan sedan användas på de flesta platser där en typ förväntas, till exempel Arrays och generiska krav.
Protokollmedlemmar delar alltid samma åtkomstkvalificering som hela protokollet och kan inte specificeras separat. Även om ett protokoll kan begränsa åtkomst med krav på getter eller setter, enligt exemplen ovan.
Mer information om protokoll finns i The Swift Programming Language .
Objektiv-C-protokoll liknar Swift-protokoll.
Protokoll är också jämförbara med Java-gränssnitt .
Grunder om protokoll
Om protokoll
Ett protokoll anger initialiserare, egenskaper, funktioner, prenumerationer och tillhörande typer som krävs av en Swift-objekttyp (klass, struktur eller enum) som överensstämmer med protokollet. På vissa språk är liknande idéer för kravspecifikationer för efterföljande objekt kända som "gränssnitt".
Ett deklarerat och definierat protokoll är en typ, i sig själv, med en signatur av de angivna kraven, något liknande det sätt på vilket Swift-funktioner är en typ baserat på deras signatur av parametrar och returer.
Swift Protocol-specifikationer kan vara valfria, uttryckligen krävda och / eller ges standardimplementeringar via en anläggning som kallas Protocol Extensions. En snabb objekttyp (klass, struktur eller enum) som vill överensstämma med ett protokoll som är utsläppt med tillägg för alla dess specificerade krav behöver endast ange sin önskan att uppfylla för att vara i full överensstämmelse. Standardimplementeringsfaciliteten för protokolltillägg kan räcka för att uppfylla alla skyldigheter att följa ett protokoll.
Protokoll kan ärvas av andra protokoll. Detta, i samband med protokollförlängningar, innebär att protokoll kan och bör ses som ett viktigt inslag i Swift.
Protokoll och förlängningar är viktiga för att realisera Swifts bredare mål och strategier för att utforma flexibilitet och utvecklingsprocesser. Det huvudsakliga angivna syftet med Swifts protokoll och förlängningsförmåga är att underlätta kompositionsdesign i programarkitektur och utveckling. Detta kallas protokollorienterad programmering. Krossiga gamla tidtagare anser att detta är överlägset med fokus på OOP-design.
Protokoll definierar gränssnitt som kan implementeras av valfri struktur , klass eller 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
}
Egenskaper som definieras i protokoll måste antingen antecknas som { get }
eller { get set }
. { get }
innebär att egenskapen måste vara gettable, och därför kan den implementeras som alla typer av fastigheter. { get set }
innebär att egenskapen måste vara inställbar såväl som möjlig att fastställa.
En struktur, klass eller enum kan överensstämma med ett protokoll:
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
}
Ett protokoll kan också definiera en standardimplementering för något av dess krav genom en tillägg :
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
}
}
Ett protokoll kan användas som en typ , förutsatt att det inte har associatedtype
typkrav :
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]
Du kan också definiera en abstrakt typ som överensstämmer med flera protokoll:
Med Swift 3 eller bättre görs detta genom att separera listan över protokoll med en ampersand ( &
):
func doStuff(object: MyProtocol & AnotherProtocol) {
// ...
}
let items : [MyProtocol & AnotherProtocol] = [MyStruct(), MyClass(), MyEnum.caseA]
Äldre versioner har syntaxprotokoll protocol<...>
där protokollen är en kommaseparerad lista mellan vinkelfästena <>
.
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]
Befintliga typer kan utvidgas till att överensstämma med ett protokoll:
extension String : MyProtocol {
// Implement any requirements which String doesn't already satisfy
}
Tillhörande typkrav
Protokoll kan definiera tillhörande typkrav med hjälp av associatedtype
typsökord:
protocol Container {
associatedtype Element
var count: Int { get }
subscript(index: Int) -> Element { get set }
}
Protokoll med tillhörande typkrav kan endast användas som generiska begränsningar :
// 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 }
En typ som överensstämmer med protokollet kan tillfredsställa ett krav på associatedtype
typ implicit genom att tillhandahålla en given typ där protokollet förväntar sig att associatedtype
typ ska visas:
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")
(Observera att för att lägga till klarhet för att detta exempel är den generiska platshållare typ som heter T
- ett mer passande namn skulle vara Element
, som skulle skugga protokollets associatedtype Element
. Kompilatorn kommer fortfarande dra slutsatsen att den generiska platshållare Element
används för att tillfredsställa associatedtype Element
.)
En associatedtype
typealias
kan också tillfredsställas uttryckligen genom användning av en typealias
:
struct ContainerOfOne<T>: Container {
typealias Element = T
subscript(index: Int) -> Element { ... }
// ...
}
Detsamma gäller för tillägg:
// 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))
}
}
}
}
Om den överensstämmande typen redan uppfyller kravet behövs ingen implementering:
extension Array: Container {} // Array satisfies all requirements, including Element
Delegerat mönster
En delegat är ett vanligt designmönster som används i ramarna för Cocoa och CocoaTouch, där en klass delegerar ansvaret för att implementera viss funktionalitet till en annan. Detta följer en princip om separering av oro, där ramklassen implementerar generisk funktionalitet medan en separat delegatinstans implementerar det specifika användningsfallet.
Ett annat sätt att undersöka delegatmönster är när det gäller objektkommunikation. Objects
behöver ofta prata med varandra och för att göra det måste ett objekt överensstämma med ett protocol
för att bli delegat för ett annat objekt. När denna installation har gjorts talar det andra objektet tillbaka till sina delegater när intressanta saker händer.
Exempelvis bör en vy i användargränssnittet för att visa en lista med data endast vara ansvarig för logiken för hur data visas, inte för att bestämma vilken data som ska visas.
Låt oss dyka in i ett mer konkret exempel. om du har två klasser, en förälder och ett barn:
class Parent { }
class Child { }
Och du vill meddela föräldern om en förändring från barnet.
I Swift implementeras delegater med hjälp av en protocol
och så kommer vi att förklara ett protocol
som delegate
kommer att genomföra. Här är delegaten parent
.
protocol ChildDelegate: class {
func childDidSomething()
}
Barnet måste förklara en egendom för att lagra referensen till delegaten:
class Child {
weak var delegate: ChildDelegate?
}
Observera att den variabla delegate
är ett valfritt och protokollet ChildDelegate
markeras för att endast implementeras efter klasstyp (utan detta kan delegate
inte förklaras som en weak
referens för att undvika någon behållningscykel. Detta betyder att om delegate
inte längre är längre refereras någon annanstans, kommer det att släppas). Detta är så att förälderklassen bara registrerar delegaten när den behövs och är tillgänglig.
Också för att markera vår delegat som weak
måste vi begränsa vår ChildDelegate protokoll till referenstyper genom att lägga till class
sökord i protokolldeklaration.
I det här exemplet, när barnet gör något och måste meddela sin förälder, kommer barnet att ringa:
delegate?.childDidSomething()
Om delegaten har definierats meddelas delegaten att barnet har gjort något.
Förälderklassen måste utöka ChildDelegate
protokollet för att kunna svara på dess handlingar. Detta kan göras direkt på förälderklassen:
class Parent: ChildDelegate {
...
func childDidSomething() {
print("Yay!")
}
}
Eller använda ett tillägg:
extension Parent: ChildDelegate {
func childDidSomething() {
print("Yay!")
}
}
Föräldern måste också berätta för barnet att det är barnets delegat:
// In the parent
let child = Child()
child.delegate = self
Som standard tillåter inte ett Swift- protocol
en valfri funktion att implementeras. Dessa kan endast specificeras om ditt protokoll är markerat med @objc
attributet och den optional
modifieraren.
Till exempel implementerar UITableView
det generiska beteendet för en tabellvy i iOS, men användaren måste implementera två UITableViewDelegate
kallas UITableViewDelegate
och UITableViewDataSource
som implementerar hur de specifika cellerna ser ut och uppför sig.
@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) ... }
Du kan implementera detta protokoll genom att ändra din klassdefinition, till exempel:
class MyViewController : UIViewController, UITableViewDelegate
Alla metoder som inte är markerade som optional
i protokolldefinitionen ( UITableViewDelegate
i detta fall) måste implementeras.
Protokollförlängning för en specifik överensstämmande klass
Du kan skriva standardprotokollimplementeringen för en viss klass.
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"
Använda RawRepresentable-protokollet (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")
}
Denna struktur kan utvidgas någon annanstans för att lägga till fall
extension NotificationName {
static let documentationLaunched = NotificationNames(rawValue: "DocumentationLaunchedNotification")
}
Och ett gränssnitt kan utforma runt alla typer av RawRepresentable eller specifikt din enumstruktur
func post(notification notification: NotificationName) -> Void {
// use notification.rawValue
}
På samtalssidan kan du använda punktsyntax-kort för typsäker meddelandenamn
post(notification: .dataFinished)
Använda generisk RawRepresentable-funktion
// 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
}
Protokoll för endast klass
Ett protokoll kan ange att endast en klass kan genomföra det genom att använda class
sökord i sin arvslistan. Detta nyckelord måste visas före andra ärvda protokoll i denna lista.
protocol ClassOnlyProtocol: class, SomeOtherProtocol {
// Protocol requirements
}
Om en icke- ClassOnlyProtocol
försöker implementera ClassOnlyProtocol
kommer ett kompilatorfel att genereras.
struct MyStruct: ClassOnlyProtocol {
// error: Non-class type 'MyStruct' cannot conform to class protocol 'ClassOnlyProtocol'
}
Andra protokoll kan ärva från ClassOnlyProtocol
, men de kommer att ha samma klasskrav.
protocol MyProtocol: ClassOnlyProtocol {
// ClassOnlyProtocol Requirements
// MyProtocol Requirements
}
class MySecondClass: MyProtocol {
// ClassOnlyProtocol Requirements
// MyProtocol Requirements
}
Referenssemantik för endast klassprotokoll
Om du använder ett klassprotokoll kan endast referenssemantik användas när den överensstämmande typen är okänd.
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"
}
I det här exemplet, eftersom Foo
är ett klassprotokoll, är tilldelningen till bar
giltig eftersom kompilatorn vet att foo
är en klasstyp och därför har referenssemantik.
Om Foo
inte var en klass endast protokoll skulle en kompilator felet gav - som överensstämmer typ kan vara ett värde typ , vilket skulle kräva en var
anteckning för att vara föränderlig.
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
}
Svaga variabler av protokolltyp
När du använder den weak
modifieraren på en variabel av protokolltyp måste den protokolltypen endast vara klass, eftersom weak
endast kan tillämpas på referenstyper.
weak var weakReference : ClassOnlyProtocol?
Implementera Hashable-protokoll
Typer som används i Sets
och Dictionaries(key)
måste överensstämma med Hashable
protokollet som ärver från Equatable
protokollet.
Anpassad typ som Hashable
protokollet måste implementeras
- En beräknad fastighet
hashValue
- Definiera en av jämställdhetsoperatörerna, dvs
==
eller!=
.
Följande exempel implementerar Hashable
protokollet för en anpassad 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))
Obs : Det är inte nödvändigt att olika värden i anpassad typ har olika hashvärden, kollisioner är acceptabla. Om hashvärden är lika kommer jämställdhetsoperatören att användas för att bestämma verklig jämlikhet.