Protocol oriented programming
You’ve probably heard of object-oriented programming, functional programming, generic programming, and apple’s new protocol-oriented programming, all of which can be understood as a programming paradigm. Programming paradigms are the ideas behind a programming language that represent how the language’s author wants to solve a problem.
Different paradigms are reflected in the real world, where different programming languages are applicable to different domains and environments. For example, in object-oriented programming, developers use objects to describe everything and try to solve all possible problems with objects. Programming paradigms have their own preferences and limitations, so more and more modern programming languages are beginning to support multiple paradigms, making the language itself stronger and more adaptable.
Protocol oriented programming is evolved on the basis of object oriented programming, which abstracts data types encountered in the process of programming from base classes to protocols. For example, a cat, a dog, it’s easy to think of extracting a base class that describes an animal. That’s object-oriented programming. Of course, some people think of extracting a protocol that is common to animals, and that is protocol oriented programming.
In Swift, the protocol is given more functions and more space to use, adding extended functions to the protocol, making it capable of data type abstraction in most cases, so Apple began to claim Swift as a protocol-oriented programming language.
Protocol based
Definition of official document:
Protocols define blueprints for methods, attributes, and other specific task requirements or capabilities. A protocol can be adopted by a class, structure, or enumeration type to provide a concrete implementation of the desired functionality. Any type that satisfies the requirements of the protocol is said to comply with the protocol.
Definition of protocol
protocol Food { }
Copy the code
Declare a protocol named Food using the keyword protocol.
Defining protocol properties
protocol Pet {
var name: String { get set }
var master: String { get }
static var species: String { get }
}
Copy the code
Defining attributes in a protocol indicates that the types that comply with the protocol have certain attributes.
-
Only the var keyword can be declared;
-
You need to specify whether the property is readable {get} or readable and writable {get set};
-
Static is used to define type methods, type attributes, and type subscripts. Static is used to define type methods, type attributes, and type subscripts.
-
Attribute cannot be assigned an initial value;
Struct Dog: Pet {var name: String var master: String static var species: String = "" var color: UIColor? = nil} var dog = dog (name: "rich ", master:" small ") dogCopy the code
Define an inherited protocol structure, Dog, and add a color attribute.
-
Static modifier class attributes must have an initial value or implement the get set method (‘static var’ declaration requires an Initializer expression or getter/setter) Specifiers)
-
Why does set not report an error?
If dog.master == "" {dog.master =" "}Copy the code
The master property is defined in the protocol as a read-only property get. Why can the above code also be set?
The “read Only” attribute in a protocol modifies instances of this “type” of protocol.
Master = master = master = master = masterCopy the code
Although we cannot create an instance of a protocol directly as we can create an instance of a class, we can “assign” an instance of a protocol. Cannot assign to property: ‘master’ is a get-only property.
[bug Mc-10862] – Dog added Pet property that does not exist var color: UIColor? = nil, will not appear in PET.
Define protocol methods
The protocol in Swift can define class methods or instance methods that, in the type that complies with the protocol, implement specific method details that are invoked by class or instance.
protocol Pet { var name: String { get set } var master: String { get } static var species: Static func sleep() mutating func changeName()} struct Dog: Pet {var name: Var master: String static var species: String = "" var color: UIColor? = nil static func sleep() {print(" ")} mutating func changeName() {name = ""}}Copy the code
-
The parameters of declared protocol methods cannot have default values
❌ func changeName(name: String = "rhubarb ") // Swift considers the default to be a disguised implementationCopy the code
-
When a method in a structure modifies a property, it must be preceded by the keyword mutating to indicate that the property can be modified. This method is called the mutation method.
Initializers in protocols
When each pet is adopted, the owner has been determined:
Init (master: String)} struct Dog: Pet {required init(master: String) String) { self.master = master } }Copy the code
An error is reported returning all properties from the initializer without initializing all storage properties. Return from initializer without initializing all stored properties. Add self.name = “”.
class Cat: Pet {
required init(master: String) {
self.master = master
self.name = ""
}
}
Copy the code
The Cat class complies with this protocol, and the initializer must be decorated with the required keyword for the concrete implementation of the initializer.
Inheritance and compliance agreements
class SomeClass: NSObject, OneProtocol, TwoProtocol { }
Copy the code
Because the inheritance of the class in Swift is single, but the class can abide by multiple protocols, in order to highlight the particularity of its single parent, the inherited parent should be placed first and the protocols followed in turn.
Multiple protocol method names conflict
protocol ProtocolOne { func method() -> Int } protocol ProtocolTwo { func method() -> String } struct PersonStruct: ProtocolOne, ProtocolTwo { func method() -> Int { return 1 } func method() -> String { return "Hello World" } } let ps = PersonStruct() // Try to call a method that returns Int let num = ps.method() ❌ // Try to call a method that returns String let String = ps.method() ❌ let num = (ps as) ProtocolOne).method() ✅ let string = (ps as ProtocolTwo).method() ✅Copy the code
The compiler has no way of knowing which protocol the method() method of the same name is, so it needs to specify a method() method that calls a particular protocol.
Optional implementation of protocol methods
-
Method 1: Use optional
@objc protocol OptionalProtocol { @objc optional func optionalMethod() func requiredMethod() } Copy the code
-
Method 2: Use Extension to do the default processing
protocol OptionalProtocol { func optionalMethod() func requiredMethod() } extension OptionalProtocol { func optionalMethod() { } } Copy the code
Inheritance and aggregation of protocols
Inheritance of agreement
A protocol can inherit from one or more other protocols and add more requirements to its inheritance. The syntax for protocol inheritance is similar to that for class inheritance. Select protocols that list multiple inheritance, separated by commas.
protocol OneProtocol { }
protocol TwoProtocol { }
protocol ThreeProtocol: OneProtocol, TwoProtocol { }
Copy the code
A new protocol is generated that has the methods or properties of OneProtocol and TwoProtocol. Methods or properties required to implement OneProtocol and TwoProtocol.
Aggregation of protocols
Implement protocol aggregation (composition) to combine multiple protocols into a requirement using a form such as OneProtocol & TwoProtocol.
protocol OneProtocol { }
protocol TwoProtocol { }
typealias FourProtocol = OneProtocol & TwoProtocol
Copy the code
The aggregate is not a new protocol, just a proxy, proxy collection of these protocols.
The difference between inheritance and aggregation of protocols
First of all, the inheritance of the protocol defines a new protocol, and we hope that it will be widely used. Aggregation and agreement, it does not define new fixed protocol type, on the contrary, it is just a temporary definition has the agreement calls for all aggregation of local agreement, is likely to be “one-time demand”, using the aggregation protocol ensures the conciseness and readability of the code, at the same time in addition to defining new types of unnecessary tedious, And defined and used so close together, see Knowingly, also known as anonymous protocol aggregation. However, aggregation using anonymous protocols can express less information, so it needs to be used at the discretion of developers.
Check of protocol
If pig is Pet {print(" comply with Pet protocol ")}Copy the code
Verify that PIG is an instance of complying with the Pet protocol type.
Protocol specification
Protocol ClassProtocol: class {} struct Test: ClassProtocol {Copy the code
Use the class keyword to define a protocol that can only be followed by the class. Non-class type ‘Test’ cannot conform to class protocol ‘ClassProtocol’.
Protocol as parameter
func update(param: FourProtocol) { }
Copy the code
Using the protocol as a parameter indicates that an instance that complies with the protocol is available as a parameter.
Protocol association type
The association type of a protocol refers to the type of some attributes in the protocol that have the same logic but different types. You can use the keyword associatedType to declare the association type for these attributes.
protocol LengthMeasurable { associatedtype LengthType var length: LengthType { get } func printMethod() } struct Pencil: LengthMeasurable { typealias LengthType = CGFloat var length: CGFloat func printMethod() {print(" length = \(length) cm ")}} struct Bridge: ____5__ {typeAlias LengthType = Int var length: Int funtmethod () {print(" length = \(length) m ")}}Copy the code
The Length__4__ protocol defines a type generic with associatedType. When implementing a protocol, define specific types. This can be adapted to measure the length of various objects.
Distinction between AssociatedType and TypeAlias
- Associatedtype: When defining a protocol, it can be used to declare one or more types as part of the protocol definition. This association type provides a custom name for a type in the protocol whose actual type or meaning is specified when the protocol is implemented.
- Typealias: Rename existing types (including system and custom), then you can use this alias to replace the original type, has achieved improved readability, and can be renamed according to the business from the actual programming, can express practical meaning.
Extension of protocol
Consider this scenario: one person enters a competition and three judges score. At the end of the game, find the average score of this person.
protocol Score { var name: String { get set } var firstJudge: CGFloat { get set } var secondJudge: CGFloat { get set } var thirdJudge: CGFloat { get set } func averageScore() -> String } struct Xiaoming: Score { var firstJudge: CGFloat var secondJudge: CGFloat var thirdJudge: CGFloat func averageScore() -> String { let average = (firstJudge + secondJudge + thirdJudge) / 3 return }} let xiaoming = xiaoming (name: "xiaoming ", firstJudge: 80, secondJudge: 90, thirdJudge: 100) let average = xiaoming.averageScore()Copy the code
If there are 10 players in this game, the way to calculate the average, you have to write it 10 times. Code duplication is severe. The problem can be solved by extending the protocol.
extension Score { func averageScore() -> String { let average = (firstJudge + secondJudge + thirdJudge) / 3 return "\(name) = \(average)"}}Copy the code
AverageScore is implemented by default and does not need to be complied with.
At this time, the organizer of the competition wants to count everyone’s highest score, what should be done?
extension Score {
func maxScore() -> CGFloat {
return max(firstJudge, secondJudge, thirdJudge)
}
}
let maxScore = xiaoming.maxScore()
Copy the code
The organizers were not satisfied with the output. Trying to prefix Xiao Ming’s score to 90.0.
// Extension CustomStringConvertible {var customDescription: String {return "+ description}} print(xiaoming. AverageScore ().customDescription)Copy the code
Conclusion:
- Provides default implementations of certain properties and methods in the protocol through protocol extensions.
- Unifying common code and attributes greatly increases code reuse.
- Extensions for system/custom protocols.
Swift 55 standard library protocol
Swift’s 55 standard library protocols can be divided into three categories
type | describe | mark |
---|---|---|
“Can do” agreement | (capability) describes something that a type can do or has done. | In order to-ableAt the end |
“Is a” agreement | (representing identity) describes what the type is, compared to the “Can do” protocol, these are more identity-based, representing the type of identity. | In order to-typeAt the end |
“Can be” agreement | This type can be converted to or converted to something else. | In order to-ConvertibleAt the end |
How to better naming protocol?
When customizing a protocol, you should follow Apple’s naming rules as much as possible to facilitate efficient cooperation between developers.
55 standard Swift protocols
55 standard Swift protocol addresses (to be done)
Advantages of protocol programming
Object Orientation (inheritance)
There is a requirement that the Logo image displayed on a page needs to be rounded to make it look better.
logoImageView.layer.cornerRadius = 5
logoImageView.layer.masksToBounds = true
Copy the code
If required, all logos throughout the APP should be rounded. The easiest solution is to define a class called LogoImageView that initializes the Logo object.
class LogoImageView: UIImageView { init(radius: CGFloat = 5) { super.init(frame: CGRect.zero) layer.cornerRadius = radius layer.masksToBounds = true } required init? (coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }Copy the code
If all logos are required to support the click jitter effect.
class LogoImageView: UIImageView { init(radius: CGFloat = 5) { super.init(frame: CGRect.zero) layer.cornerRadius = radius layer.masksToBounds = true isUserInteractionEnabled = true let tap = UITapGestureRecognizer.init(target: self, action: #selector(shakeEvent)) addGestureRecognizer(tap) } required init? (coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } extension LogoImageView { @objc func shakeEvent() { Animation = CAKeyframeAnimation() animation.keypath = "transform" animation.duration = 0.25 let origin = CATransform3DIdentity let minimum = CATransform3DMakeScale(0.8, 0.8, 1) Let originValue = NSValue(caTransform3D: origin) let minimumValue = NSValue(caTransform3D:minimum) animation.values = [originValue, minimumValue, origin, minimumValue, origin] layer.add(animation, forKey: "bounce") layer.transform = origin } }Copy the code
At this point, if other functions also want to use dither animation, you have to accept the rounded corners function. Even if the cutaway function is removed from the initialization method as an optional method, this coupling code has to be accepted.
Some projects have a parent class that inherits UIViewController, does a lot of things, and all the pages in the project have to inherit from it. And it’s just too convenient to stuff code into this custom UIViewController, and the class tends to get bloated and ossified and harder to maintain with iteration. The following subclass code all depends on this parent class, which is very difficult to extract and reuse.
The project uses a mix of native and H5 features. The parent class of the H5 container defined, WebViewController, must meet the following service requirements:
- Some H5 pages are relatively simple, just need to display the page normally.
- Some require JS code injection.
- Some need to provide to save pictures to album for H5 use.
- Some need to provide the original page for H5 to use.
This parent class handles WKWebView implementation, JS injection, bridge definition and bridge function implementation, and many other capabilities. Resulting in thousands of lines of WebViewController code. Extensions are difficult to maintain.
Using inheritance to solve the problem of reuse, it is easy to bring code coupling.
If UILabel also needs jitter animations, inheritance won’t do it. UIImageView and UILabel are already subclasses of UIView, and unless you change UIView, you can’t do that by inheritance.
Object Orientation (Extension)
Extensions allow you to add new methods to a class without modifying its implementation file. You can address the need for jitter in both UIImageView and UILabel by adding an extension to UIView. The downside is that adding this to a class pollutes all the objects in that class. (UIButton says: I don’t need it. Why?)
Object Oriented (utility classes)
Of course, we can simply write a utility class to implement the jitter effect, and then pass the necessary parameters (layer). The disadvantage is that it is relatively cumbersome to use, and numerous tool classes are difficult to manage. (Have you ever had a headache because you didn’t know which tool class to use? Have you ever had a headache because of which tool class to put a method in? Ever get a headache from too much utility code?
For agreement
Through the protocol to achieve the function of cutting corners and jitter animation.
Protocol RoundCornerable {func roundCorner(radius: CGFloat)} /// Add a default implementation to this protocol method by extending it so that classes that comply with the protocol inherit FROM UIView. extension RoundCornerable where Self: UIView { func roundCorner(radius: CGFloat) {layer.cornerradius = radius layer.maskstobounds = true}} protocol Shakeable {func StartShake ()} /// implement the protocol method content and specify that only LogoImageView can be used. extension Shakeable where Self: LogoImageView { func startShake() { let animation = CAKeyframeAnimation() animation.keyPath = "transform" Animation. duration = 0.25 let origin = CATransform3DIdentity let minimum = CATransform3DMakeScale(0.8, 0.8, 1) let originValue = NSValue(caTransform3D: origin) let minimumValue = NSValue(caTransform3D:minimum) animation.values = [originValue, minimumValue, origin, minimumValue, origin] layer.add(animation, forKey: "Bounce ") layer.transform = origin}} /// all the cornerable features are required. You only have the shake animation if you follow the Shakeable protocol. class LogoImageView: UIImageView, RoundCornerable, Shakeable { init(radius: CGFloat = 5) { super.init(frame: CGRect.zero) roundCorner(radius: radius) isUserInteractionEnabled = true let tap = UITapGestureRecognizer.init(target: self, action: #selector(shakeEvent)) addGestureRecognizer(tap) } required init? (coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @objc func shakeEvent() { startShake() } }Copy the code
Generics and generic constraints
Sometimes scenarios declare protocols that are only used by a subset of objects.
Shakeable Where Self: LogoImageView {} Shakeable Shakeable where Self: LogoImageView {}Copy the code
Protocol solves the thorny superclass problem in object orientation
This module references protocols common to the Swift standard library. Because the example is too good, I quote it directly.
As a kind of bird, sparrows should inherit birds, but if they inherit birds, it is equivalent to tacitly accepting that sparrows are a kind of pets, which is obviously illogical. The sparrow is in an awkward position. The general approach to this problem is as follows
At first glance, it seems to solve such a problem, but after careful consideration, since Swift only supports single inheritance, sparrows cannot embody the characteristics of sparrows as a bird (such as flying) without inherits birds. If there is a new aircraft class, although there is no connection between aircraft and pets, but planes and birds have many characteristics in common (such as flying), how should such characteristics be reflected? The answer is to create a new class to be the parent of animals and airplanes.
Object-oriented is such a layer up new parent class finally got a super parent class NSObject. Although the problem was solved, the commonality between sparrows and birds, or planes and birds, was not well represented. The agreement is designed to solve just such problems.
In fact, the relationship between animals, birds, planes and other classes in the figure above should be the inheritance relationship as shown in the figure above. Using protocols treats “pet”, “fly”, etc., as a trait, or describes the category from another dimension, and more importantly does not break the inherited parent-child relationship between the original categories.
The Flyable codes are centrally located, and the ability to fly is required to comply with the agreement; Pet-related code is unified in PetType, and you need to be a pet to comply with this protocol. these
Protocol is flexible, combined with the inherent inheritance between the original object-oriented classes, perfect description of the world.
The literature
SwiftDoc.org
Common protocols in the Swift standard library
Swift Summit – What the 55 Swift Standard Library Protocols Taught Me (Needed VPN)