Swift Protocols
Protocols define the methods and properties required to implement a specific functionality.
Any type that satisfies the requirements of a protocol is said to conform to that protocol.
Classes, structures, or enumeration types can conform to a protocol and provide concrete implementations to fulfill the methods and functionalities defined by the protocol.
Syntax
The syntax for defining a protocol is as follows:
protocol SomeProtocol {
// Protocol contents
}
To make a class conform to a protocol, the protocol name is added after the type name, separated by a colon :
, as part of the type definition. When conforming to multiple protocols, they are separated by commas ,
.
struct SomeStructure: FirstProtocol, AnotherProtocol {
// Structure contents
}
If a class conforms to a protocol and also has a superclass, the superclass name should precede the protocol names, separated by a comma.
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// Class contents
}
Property Requirements
Protocols specify particular instance or class properties without specifying whether they are stored or computed properties. Additionally, they must indicate whether the properties are read-only or read-write.
Variables are typically declared in protocols using var
, and the property is marked as read-write with { set get }
after the type declaration, or read-only with { get }
.
protocol classa {
var marks: Int { get set }
var result: Bool { get }
func attendance() -> String
func markssecured() -> String
}
protocol classb: classa {
var present: Bool { get set }
var subject: String { get set }
var stname: String { get set }
}
class classc: classb {
var marks = 96
let result = true
var present = false
var subject = "Swift Protocols"
var stname = "Protocols"
func attendance() -> String {
return "The \(stname) has secured 99% attendance"
}
func markssecured() -> String {
return "\(stname) has scored \(marks)"
}
}
let studdet = classc()
studdet.stname = "Swift"
studdet.marks = 98
studdet.markssecured()
print(studdet.marks)
print(studdet.result)
print(studdet.present)
print(studdet.subject)
print(studdet.stname)
The output of the above program is:
98
true
false
Swift Protocols
Swift
Mutating Method Requirements
Sometimes it is necessary to modify the instance of the type within the method.
For instance, for value types (structures, enumerations), the mutating
keyword is used as a prefix before func
to indicate that the method can modify the instance it belongs to and its properties.
protocol daysofaweek {
mutating func show()
}
enum days: daysofaweek {
case sun, mon, tue, wed, thurs, fri, sat
mutating func show() {
switch self {
case .sun:
self = .sun
print("Sunday")
case .mon:
self = .mon
print("Monday")
case .tue:
self = .tue
print("Tuesday")
case .wed:
self = .wed
print("Wednesday")
case .thurs:
self = .thurs
print("Thursday")
case .fri:
self = .fri
print("Friday")
case .sat:
self = .sat
print("Saturday")
}
}
}
switch self {
case .thurs:
self = .thurs
print("Wednesday")
case .fri:
self = .fri
print("Friday")
case .sat:
self = .sat
print("Saturday")
default:
print("NO Such Day")
}
The above program execution output is:
Wednesday
Protocol Constructor Requirements
Protocols can require conforming types to implement specific constructors.
You can write constructor declarations in the protocol definition as if you were writing a regular constructor, but without the curly braces and the constructor body. The syntax is as follows:
protocol SomeProtocol {
init(someParameter: Int)
}
Example
protocol TCPProtocol {
init(aprot: Int)
}
Protocol Constructor Requirements Implementation in Classes
You can implement constructors in a class that conforms to a protocol, specifying them as either designated or convenience constructors. In both cases, you must mark the constructor implementation with the "required" modifier:
class SomeClass: SomeProtocol {
required init(someParameter: Int) {
// Constructor implementation
}
}
protocol TCPProtocol {
init(aprot: Int)
}
class TCPClass: TCPProtocol {
required init(aprot: Int) {
}
}
Using the required modifier ensures that all subclasses that conform to the protocol also provide an explicit or inherited implementation of the constructor requirement.
If a subclass overrides a designated constructor from its superclass and that constructor also conforms to a protocol requirement, then the constructor implementation needs to be marked with both the required and override modifiers:
protocol TCPProtocol {
init(no1: Int)
}
class MainClass {
var no1: Int // local variable
init(no1: Int) {
self.no1 = no1 // initialization
}
}
class SubClass: MainClass, TCPProtocol {
var no2: Int
init(no1: Int, no2: Int) {
self.no2 = no2
super.init(no1: no1)
}
// Required by protocol and overridden from superclass
required override convenience init(no1: Int) {
self.init(no1: no1, no2: 0)
}
}
let res = MainClass(no1: 20)
let show = SubClass(no1: 30, no2: 50)
print("res is: \(res.no1)")
print("res is: \(show.no1)")
print("res is: \(show.no2)")
The above program execution output is:
res is: 20
res is: 30
res is: 50
Protocol Types
Although protocols do not implement any functionality themselves, they can be used as types.
Protocols can be used like any other ordinary types in various scenarios:
- As parameter types or return types in functions, methods, or initializers
- As types for constants, variables, or properties
- As element types in arrays, dictionaries, or other containers
Example
protocol Generator {
associatedtype members
func next() -> members?
}
var items = [10, 20, 30].makeIterator()
while let x = items.next() {
print(x)
}
for lists in [1, 2, 3].map({ i in i * 5 }) {
print(lists)
}
print([100, 200, 300])
print([1, 2, 3].map({ i in i * 10 }))
The above program execution output is:
10
20
30
5
10
15
[100, 200, 300]
[10, 20, 30]
Adding Protocol Members in Extensions
We can extend existing types (classes, structures, enumerations, etc.) through extensions.
Extensions can add properties, methods, subscripts, protocols, and other members to existing types.
protocol AgeClassificationProtocol {
var age: Int { get }
func ageType() -> String
}
class Person {
let firstName: String
let lastName: String
var age: Int
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
self.age = 10
}
}
extension Person: AgeClassificationProtocol {
func fullName() -> String {
var c: String
c = firstName + " " + lastName
return c
}
func ageType() -> String {
switch age {
case 0...2:
return "Baby"
case 2...12:
return "Child"
case 13...19:
return "Teenager"
case let x where x > 65:
return "Elderly"
default:
return "Normal"
}
}
}
Protocol Inheritance
Protocols can inherit one or more other protocols and can add new requirements on top of the inherited protocols.
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
// protocol definition
}
Example
protocol ClassA {
var no1: Int { get set }
func calc(sum: Int)
}
protocol Result {
func print(target: ClassA)
}
class Student2: Result {
func print(target: ClassA) {
target.calc(1)
}
}
class ClassB: Result {
func print(target: ClassA) {
target.calc(5)
}
}
class Student: ClassA {
var no1: Int = 10
func calc(sum: Int) {
no1 -= sum
print("学生尝试 \(sum) 次通过")
if no1 <= 0 {
print("学生缺席考试")
}
}
}
class Player {
var stmark: Result!
init(stmark: Result) {
self.stmark = stmark
}
func print(target: ClassA) {
stmark.print(target)
}
}
var marks = Player(stmark: Student2())
var marksec = Student()
marks.print(marksec)
marks.print(marksec)
marks.print(marksec)
marks.stmark = ClassB()
marks.print(marksec)
marks.print(marksec)
marks.print(marksec)
The output of the above program is:
学生尝试 1 次通过
学生尝试 1 次通过
学生尝试 1 次通过
学生尝试 5 次通过
学生尝试 5 次通过
学生缺席考试
学生尝试 5 次通过
学生缺席考试
Class-Only Protocols
You can limit a protocol to be adopted only by class types by adding the class
keyword in the protocol's inheritance list.
The class
keyword must be the first in the protocol's inheritance list, followed by any other inherited protocols. The format is as follows:
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
// class-only protocol definition
}
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
// Protocol definition
}
Example
protocol TcpProtocol {
init(no1: Int)
}
class MainClass {
var no1: Int // Local variable
init(no1: Int) {
self.no1 = no1 // Initialization
}
}
class SubClass: MainClass, TcpProtocol {
var no2: Int
init(no1: Int, no2: Int) {
self.no2 = no2
super.init(no1: no1)
}
// Required by protocol, overridden from superclass
required override convenience init(no1: Int) {
self.init(no1: no1, no2: 0)
}
}
let res = MainClass(no1: 20)
let show = SubClass(no1: 30, no2: 50)
print("res is: \(res.no1)")
print("res is: \(show.no1)")
print("res is: \(show.no2)")
Output of the above program is:
res is: 20
res is: 30
res is: 50
Protocol Composition
Swift supports combining multiple protocols, which is useful when you need to conform to several protocols at once.
Syntax format is as follows:
protocol Stname {
var name: String { get }
}
protocol Stage {
var age: Int { get }
}
struct Person: Stname, Stage {
var name: String
var age: Int
}
func show(celebrator: Stname & Stage) {
print("\(celebrator.name) is \(celebrator.age) years old")
}
let studname = Person(name: "Priya", age: 21)
print(studname)
let stud = Person(name: "Rehan", age: 29)
print(stud)
let student = Person(name: "Roshan", age: 19)
print(student)
Output of the above program is:
Person(name: "Priya", age: 21)
Person(name: "Rehan", age: 29)
Person(name: "Roshan", age: 19)
Checking for Protocol Conformance
You can use the is
and as
operators to check for protocol conformance or to force downcast to a specific type.
- The
is
operator checks if an instance conforms to a protocol. - The
as?
operator returns an optional value, which is the protocol type if the instance conforms; otherwise, it returnsnil
. - The
as
operator is used for forced downcasting. If the cast fails, it causes a runtime error.
Example
The following example defines a HasArea protocol, requiring a readable area of type Double:
protocol HasArea {
var area: Double { get }
}
// Circle class conforms to the HasArea protocol
class Circle: HasArea {
let pi = 3.1415927
var radius: Double
var area: Double { return pi * radius * radius }
init(radius: Double) { self.radius = radius }
}
// Country class conforms to the HasArea protocol
class Country: HasArea {
var area: Double
init(area: Double) { self.area = area }
}
// Animal class does not conform to the HasArea protocol
class Animal {
var legs: Int
init(legs: Int) { self.legs = legs }
}
let objects: [AnyObject] = [
Circle(radius: 2.0),
Country(area: 243_610),
Animal(legs: 4)
]
for object in objects {
if let objectWithArea = object as? HasArea {
print("Area is \(objectWithArea.area)")
} else {
print("Something that doesn't have an area")
}
}
Output of the above program is:
Area is 12.5663708
Area is 243610.0
Something that doesn't have an area
Circle(radius: 2.0),
Country(area: 243_610),
Animal(legs: 4)
]
for object in objects {
// Check each element to see if it conforms to the HasArea protocol
if let objectWithArea = object as? HasArea {
print("Area is \(objectWithArea.area)")
} else {
print("No area")
}
}
The output of the program execution is:
Area is 12.5663708
Area is 243610.0
No area