Protocol is a very important piece of content in Swift, basically all so-called “master intermediate Swift above” can see the book/tutorial, code is protocol-oriented programming, generics, closures are widely used.
Protocol syntax
What does an agreement do? As the name suggests, it says something, if you follow the agreement, you have to follow something that’s written in the agreement.
// Define a protocol protocol DemoProtocol {// specify something}Copy the code
Of course, our custom type (struct, class, enum) can also follow multiple protocols, which are separated by commas.
struct DemoStruct: DemoProtocol, DemoProtocol2 {
}
Copy the code
If it is class and it has a parent class, we will put the parent name before the agreement, because after all, observing filial piety is a unifying virtue between China and foreign countries.
class SonClass: FatherClass, DemoProtocol {
}
Copy the code
Now that we have talked about the definition of an agreement, we need to talk about the contents of the agreement:
So what can we put in the protocol, the first thing you’re going to think about is properties and methods and things like that, right, that’s what we’re used to doing in object-oriented programming, so let’s look at the requirements for properties in the protocol.
Properties in the protocol
A protocol can require instance attributes or type attributes of a specific name and type to be provided for the type that follows the protocol.
The protocol, yes, requires that the type of the protocol (class or structure, etc.) be followed by instance properties with specific names and types. Instance properties or type properties color{red}{type properties} type properties
That’s a mouthful. Let’s look at them one by one.
This is just a little bit of a review of properties, and in Swift, there are two main categories of properties, instance properties and type properties.
Instance attributes are divided into storage instance attributes and compute instance attributes. Type attributes are divided into storage type attributes and compute type attributes.
Store instance properties \color{red}{Store instance properties} Store instance properties: Only one copy of the properties stored in the memory of the instance.
Compute instance properties \color{red}{compute instance properties} Compute instance properties: does not occupy system memory, when the call to calculate the instance properties, similar to the instance method.
Storage type attribute \color{blue}{Storage type attribute} Storage type attribute: there is only one memory during the whole program operation, similar to global variables or constants.
Calculate the type attribute \color{blue}{calculate the type attribute} Calculate the type attribute: does not occupy the system memory, when the call to calculate the property, similar to the global function.
That is, if you draft a protocol, you can ask to follow the type of the protocol and provide various attributes of a particular name and type.
But our protocol, we don’t want it to be too rigid, too rigid for the 21st century, so our protocol doesn’t specify whether a property is a stored property or a calculated property, either way, do it all. You just need to give the property a specific name and type.
However, the content we write in the agreement sometimes needs a little detail, just like discussing a contract. The style of the contract is not rigid and reasonable, but the content involved needs to be considered and specified. So, the protocol can specify whether the property is readable, or readable and writable.
In the protocol, an attribute type is followed by a pair of curly braces filled with keywords to specify its read-write type.
Protocol DemoProtocol {var demoInt: Int {get} var demoString: String {get set}Copy the code
When we define the type attribute \color{blue}{type attribute} in the protocol, we usually precede the type attribute with the static keyword. But if you’re sure that only our class type follows this protocol, we can also use the class keyword in front of the type attribute in addition to static.
protocol DemoProtocol {
static var demoTypeProperty: Int { get set }
}
Copy the code
So, when you see an agreement, you can easily infer some information:
What are the properties defined in this protocol? If it is static, it is a type property; if it is not, it is an instance property; if it is class, it is a type property, and the protocol is followed by a class type. Property has curly braces get, which is readable, and curly braces get set, which is readable and writable.
Such as:
protocol FullyNamed {
var fullName: String { get }
}
Copy the code
This protocol defines an instance attribute, fullName, which is readable. There is no requirement beyond that, and when we follow it:
Struct Person: FullName {var FullName: String Struct Person: FullName {var FullName: String } let person = Person(fullName: "DGH")Copy the code
Let’s look at a more complicated one:
class StartShip: FullyNamed { var prefix: String? var name: String init(name: String, prefix: String? = nil) { self.name = name self.prefix = prefix } var fullName: String { (prefix ! = nil ? prefix! + "" : "") + name}} var demo = StartShip(name: "", prefix: "") print(demo. FullName) //Copy the code
The read-only nature of our fullName property is reflected.
OK, so we’re done with properties in the protocol, now let’s look at another thing, methods in the protocol.
Methods in protocols
A protocol can require that certain specified instance or class methods be implemented according to the protocol type. These methods are part of the protocol and are included in the definition of the protocol just like normal methods, but without curly braces and method bodies. Methods with mutable parameters can be defined in the protocol in the same way as normal methods. However, default parameters for methods in protocols are not supported.
Official explanations are always distracting to read. Here, let’s go step by step.
First, instance methods or class methods:
In Swift, methods are divided into instance methods and class methods:
Class Demo {// instance method func printSomething() {print("something")}} Class Demo2 {class func printSomethingClass() {print("somethingClass")} static func printSomethingStatic() { Print (" somethingStatic ")}} / / class method call Demo2. PrintSomethingClass Demo2 (). The printSomethingStatic ()Copy the code
Methods with mutable parameters can be defined in the protocol in the same way that normal methods are defined. However, default parameters for methods in protocols are not supported.
This means:
protocol DemoProtocol {
func printInt (input: Int = 1) // its wrong, will popup error
func printString (input: String) // its right
}
Copy the code
So just to summarize, in the protocol we can define some methods, including instance methods and class methods, we don’t have to write curly braces and the body of the method, you can give the method default parameters if you want, but you can’t give the default parameters raw values.
If you define a class method in a protocol, you can add the static keyword before the class method as you would define a type attribute.
Variant methods in protocols
Sometimes we need to change the instance of a method. For instance, in an instance method of a value type (struct, enumeration), we prefix the method with the keyword mutating to indicate that we can change the instance of the method and the value of any attribute of the instance.
We can also define mutants in the protocol so that value types can use mutants normally when they follow the protocol.
Such as:
protocol Togglable {
mutating func toggle()
}
enum onOffSwitch: Toggleable {
case on, off
mutating func toggle() {
switch self {
case .on:
self = .off
case .off:
self = .on
}
}
}
var lightToggle = onOffSwitch.off
lightToggle.toggle() // on
Copy the code
A constructor in a protocol
We could of course write a constructor in the protocol that follows its type to implement it. We write it just like a normal constructor, but again we don’t need to write curly braces and constructor entities.
protocol SomeProtocol {
init(someParameter: Int)
}
Copy the code
When we implement a constructor in a class that follows this type, we need to prefix it with the keyword required, either specifying the constructor or facilitating the constructor
Class Demo: SomeProtocol {required init(someParameter: Int){// Implementation part}}Copy the code
Using the required modifier ensures that all subclasses must also provide this constructor implementation to comply with the protocol. If the class is already marked final, there is no need to use the required modifier in the implementation of the protocol constructor, because final classes cannot have subclasses.
Let’s imagine a situation where we have a class that looks like this:
class Father {
init() {
}
}
Copy the code
And we have an agreement:
protocol SomeProtocol {
init()
}
Copy the code
What if we write a class that is a subclass of Father, and we want it to follow SomeProtocol? We need to rewrite init for Father, and we need to implement init for SomeProtocol.
You can write:
Class SonClass: Father, SomeProtocol {required override init () {// required first, override first}}Copy the code
Protocol as type, and delegate
The protocol doesn’t implement anything, but it can be used as a type, and we’ll combine that with grooming in the delegate.
Delegate is a design pattern that allows classes or structures to delegate some of their responsibility to instances of other types. The implementation of the delegate pattern is simple: Define a protocol to encapsulate the functions that need to be outsourced, so that you can determine which types of protocol compliance provide those functions. Delegation is outsourcing, you get the idea.
Examples given by Swift officials are not friendly, confusing and difficult to understand. I found a good example on the Internet:
Protocol LogManagerDelegate {func writeLog()} // a user login class UserController {var delegate: LogManagerDelegate? // protocol as type func login() {delegate? .writelog ()}} // a class that follows LogManagerDelegate class SqliteLogManager: Func writeLog() {print(" write data to sqLite database ")}} let userController = UserController() userController.login() // Nothing happens. SqliteLogManager = sqliteLogManager () userController.delegate = sqliteLogManager userController.login() Write the data to the SQLite databaseCopy the code
To put it more simply, our UserController class (login company), which is responsible for user logins, wants to outsource logging and let other classes do it for it. So we use a delegate mechanism to implement this idea. First, we define a LogManagerDelegate protocol, which specifies a log writing method called func writeLog (), but it’s up to the outsourcing company to do that. Then, the SqliteLogManager class (outsourcing company) recently ran out of money and said, “We’re doing this job, so this class has to follow the protocol (following the contract), and it implements the Func writeLog ().
When we instantiate our UserController class, it is like that the login company has authorized a person, such as intern dou guohua, who is authorized to login the company. At this time, idiot dou guohua sees a machine login() in the company, he immediately starts this machine and thinks that it is directly finished. Then the login machine started as expected, but the background boy found that you did not fix the log writing function, angry spray bean Guohua. In despair, Dou Guohua found the outsourcing company SqliteLogManager (), which authorized cui Pengcheng, one of the top ten employees of their company. Finally, Dou Guohua found an agent. Take Cui Pengcheng to the login company (UserController.delegate = sqliteLogManager), and then start the login machine again. Cui Pengcheng represents the company with full authority. Print (” write data to sqLite database “) and insert it into login().
This is the proxy pattern, this is the use of protocols as types.
Protocol compliance in extensions
This is nothing to talk about, which means that if you add an extension to an existing type that follows a certain protocol, the effect and requirements are the same as if you followed the protocol in the original type.
Follow the agreement conditionally
For some generic types, there can be specific conditions for compliance with the protocol. In general, we list the restrictions by extension to make the generic type comply with the protocol conditionally. Such as:
protocol TextRepresentable {
var textualDescription: String { get }
}
extension Array: TextRepresentable where Element: TextRepresentable {
var textualDescription: String {
let itemAsText = self.map{ $0.textualDescription }
return "[" + itemAsText.joined(separator: ", ") + "]"
}
}
Copy the code