Protocol is a set of methods and properties for a particular task to which classes, structure, or enumeration can be conformed.
The syntax of protocol goes like this:
protocol ProtocolName{
// List of properties and methods goes here....
}
The keyword protocol
followed by the protocol name and curly braces are the building blocks of any protocol you need to write. Classes, structures, or enumeration can then conform to it like this:
class SampleClass: ProtocolName{
}
After class name, you type colon
and the super class name that this class extend from if any, followed by a list of protocols that you want to conform to with a comma separation.
We started by defining VehicleProtocol
that has a list of properties and functions that every vehicle should have. In properties, we have two types of properties: name
, which is marked as {get set}
, and canFly
, which is marked as {get}
. When you mark a property {get set}
, it means it's gettable and settable, whereas {get}
means it only gettable, in other words, it's a read-only property. Then, we added four methods, out of which three methods-numberOfWheels()
, move()
, and stop()
-are instance methods. The last one-popularBrands()
- marked as static
is a type method. Types methods can be called directly with type name, and there is no need to have instance to call it.
Then, we created two new classes, Bicycle
and Car
, which conform to VehicleProtocol
, and each one will have different implementations.
We have already covered the most important parts of protocols and how to use it, but still they have more features, and there are many things that can be done with it. We will try here to mention them one by one to see when and how we can use them.
Swift allows you mark protocol methods as mutating when it's necessary for these methods to mutate (modify) the instance value itself. This is applicable only in structures and enumerations; we call them value types. Consider this example of using mutating:
protocol Togglable{
mutating func toggle()
}
enum Switch: Togglable{
case ON
case OFF
mutating func toggle() {
switch self {
case .ON:
self = OFF
default:
self = ON
}
}
}
The Switch
enum implements the method toggle
, as it's defined in the protocol Togglable
. Inside toggle()
, we could update self-value as function marked as mutating
.
Delegation is the most commonly used design pattern in iOS. In delegation, you enable types to delegate some of its responsibilities or functions to another instance of another type. To create this design pattern, we use protocols that will contain the list of responsibilities or functions to be delegated. We usually use delegation when you want to respond to actions or retrieve or get information from other sources without needing to know the type of that sources, except that they conform to that protocol. Let's take a look at an example of how to create use delegate:
@objc protocol DownloadManagerDelegate {
func didDownloadFile(fileURL: String, fileData: NSData)
func didFailToDownloadFile(fileURL: String, error: NSError)
}
class DownloadManager{
weak var delegate: DownloadManagerDelegate!
func downloadFileAtURL(url: String){
// send request to download file
// check response and success or failure
if let delegate = self.delegate {
delegate.didDownloadFile(url, fileData: NSData())
}
}
}
class ViewController: UIViewController, DownloadManagerDelegate{
func startDownload(){
letdownloadManager = DownloadManager()
downloadManager.delegate = self
}
func didDownloadFile(fileURL: String, fileData: NSData) {
// present file here
}
func didFailToDownloadFile(fileURL: String, error: NSError) {
// Show error message
}
}
The protocol DownloadManagerDelegate
contains methods that would be called once the specific actions happen to inform the class that conforms to that protocol. The DownloadManager
class performs the download tasks asynchronously and informs the delegate with success or failure after it's completed. DownloadManager
doesn't need to know which object will use it or any information about it. The only thing it cares about is that the class should conform to the delegate protocol, and that's it.
We mentioned before that classes, structures, and enumerations could adopt protocols. The difference among them is that classes are reference types, whereas structures and enumerations are value types. If you find yourself having some specific actions that will be done only via reference types, mark it as class
only. To do so, just mark it as follows:
protocol ClassOnlyProtocol: class{
// class only properties and methods go here
}
Add a colon :
and the class
keyword to mark your protocol as class only.
Checking protocol conformance
It would be very useful to check whether an object conforms to a specific protocol or not. It's very useful when you have a list of objects, and only some of them conform to specific protocol. To check for protocol conformance, do the following:
class Rocket{
}
var movingObjects = [Bicycle(name: "B1"), Car(name:"C1"), Rocket()]
for item in movingObjects{
if let vehicle = item as? VehicleProtocol{
print("Found vehcile with name \(vehicle.name)")
vehicle.move()
}
else{
print("Not a vehcile")
}
}
We created a list of objects, and some of them conform to VehicleProtocol
that we created earlier. Inside the for-loop
we casted each item to VehicleProtocol
inside if
statement; the cast will succeed only if this item already conforms to that protocol.
You see that when you list your properties and methods in a protocol, the type that conforms to that protocol should adopt to all properties and methods. Skipping one of them will lead to a compiler error. Some protocols may contain methods or properties that are not necessary to implement, especially with delegates. Some delegate methods are meant to notify you something that you don't care about. In that case, you can mark these methods as optional. The keyword optional
can be added before properties and methods to mark them as optional. Another thing, the protocol that has optional stuff should be marked with @Objc
. Take a look at the following example:
@objc protocol DownloadManagerDelegate {
func didDownloadFile(fileURL: String, fileData: NSData)
optional func didFailToDownloadFile(fileURL: String, error: NSError)
}
It's the new version of DownloadManagerDelegate
, which marks didFailToDownloadFile
method as optional.