By Andrew Jaffee, translator: Grey S; Proofreading: Numbbbbb, WAMaker; Finalized: Pancf
For developers, complexity is the biggest enemy, so I look for new technologies that can help me manage chaos. Protocol Oriented programming (POP) in Swift is one of the “hot” approaches that has gained a lot of attention recently (at least since 2015). Here we will use Swift 4. I found POP promising when I was writing my own code. More intriguingly, Apple claims that “the heart of Swift is protocol-oriented.” I wanted to share my experience with POP in a formal report, a clear and concise tutorial on this emerging technology.
I’ll explain the key concepts, provide plenty of code examples, inevitably compare POP and OOP (object-oriented programming), and make comparisons to popular programming (FOP?). Has thrown cold water on the claim that POP is a panacea for all problems.
Protocol oriented program (oop) is a great new tool, is worth to add to your existing programming tools in the library, but nothing can take the place of the enduring basics, like the big function split into several small functions, will be big code file split into several small files, use meaningful variable names, take the time to design the architecture before knock code, Rational and consistent use of spacing and indentation, and allocation of related attributes and behaviors into classes and structures-following common sense can make all the difference in the world. If you write code that can’t be understood by your colleagues, it’s useless code.
Learning and adopting new technologies like POP don’t need to be absolutely unique. POP and OOP can not only coexist, but also assist each other. For most developers, including myself, mastering POP takes time and patience. Because POP is really important, I split the tutorial into two articles. This article will focus on introducing and explaining the Swift protocol and POP. The second article will delve into the advanced uses of POP (such as building application functionality from protocol), generic protocols, the motivation behind switching from reference types to value types, listing the pros and cons of POP, listing the pros and cons of OOP, comparing OOP and POP, explaining why “Swift is protocol oriented”, And delves into a concept called “local reasoning,” which is thought to be enhanced by the use of POP. This time we will only skim over some advanced topics.
The introduction
As software developers, managing complexity is essentially what we should be focusing on. When we try to learn POP as a new technology, you may not see an immediate return on your investment of time. But, just as you have come to know me, you will learn how POP handles complexity while providing you with another tool to control the chaos inherent in software systems.
I hear more and more talk about POP, but I don’t see much production code written in this way, in other words, I haven’t seen many people building application functionality from protocols instead of classes. It’s not just the human tendency to resist change. Learning a new paradigm and putting it into practice is easier said than done. As I write new applications, I gradually find myself using POP to design and implement functionality — organically and naturally.
With the excitement of new trends, there’s a lot of talk about replacing OOP with POP. I don’t think this will happen — and probably won’t happen at all — unless POP languages like Swift are vastly improved. I’m a pragmatist, not a fashionista. In developing the new Swift project, I’ve found that my behavior is a compromise. I made use of OOP where it made sense, and I made use of OOP where POP made sense, realizing that the two modes were not mutually exclusive. I combined the two techniques. You’ll find out what I’m talking about in this two-part POP tutorial.
I’ve been involved in OOP for a long time. In 1990, I bought a retail version of Turbo Pascal. After about a year of using OOP, I started designing, developing, and publishing object-oriented application products. I became a big fan. When I discovered that I could extend and enhance my own classes, I was thrilled. Over time, companies like Microsoft and Apple began developing large OOP based codebase such as Microsoft Foundation Classes (MFC) and.NET, as well as the iOS and OS X SDKS. Today, developers rarely need to rebuild the wheel when developing new applications. There is no perfect approach, and OOP has some disadvantages, but the advantages still outweigh the disadvantages. We’ll spend some time comparing OOP and POP.
Understand the agreement
When developers design the basic structure of a new iOS app, they almost always start with existing classes in frameworks like Foundation and UIKit. Almost every application I can think of requires some kind of user interface navigation system. Users need entry points into the application and signposts that guide them to use the application’s functionality. Browse through the apps on your iPhone or iPad.
What do you see when these apps open? I bet you see is a UITableViewController, UICollectionViewController and UIPageViewController subclasses.
When you create a new iOS project for the first time, everyone must know the following code snippet. For example, a new iOS project is based on the Single View App template in Xcode:
. import UIKit class ViewController: UIViewController { ...Copy the code
Some developers will stop here and create a fully customized interface, but most will take another step.
When iOS developers develop new applications, OOP is the most common feature, so what role does POP play here?
Do you know how I would go on? Imagine what the next major step is for most developers. That is to follow the protocol (and implement the delegate, but we’ve already discussed that).
Let me show you an example to make it easier to understand. I’m sure many of you have used UITableView. This is not a tutorial on UITableView, but you should know that in UIViewController protocol actually plays an important role. When adding a UITableView to a UIViewController, the UIViewController must follow the UITableViewDataSource and UITableViewDelegate protocols, like this:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate
Copy the code
In short, following a UITableViewDataSource allows you to populate all UITableViewDataSource cells with data, such as menu item names that provide navigation to the user. With a UITableViewDelegate, you can have more fine-grained control over the user’s interaction with a UITableView, such as performing appropriate actions when the user clicks on a particular UITableView cell.
define
I find that understanding common term definitions helps readers better understand a topic before getting into technical definitions and discussion. First, let’s consider the popular definition of the word “agreement” :
… A system of formal procedures or rules governing the affairs of state or diplomacy. … An accepted or established procedure or code of conduct for any group, organization or situation. … Procedures for conducting scientific experiments…
The statement in Apple’s Swift Programming Language (Swift 4.0.3) documentation:
A protocol defines a blueprint for methods, attributes, and other requirements suitable for a particular task or function. Classes, structs, or enumerations can then follow the protocol to provide an actual implementation of these requirements. Any type that meets the requirements of the protocol is said to comply with the protocol.
Protocols are one of the most important tools, and we must bring some order to the inherent chaos of software. Protocols enable us to require one or more classes and structures to contain specific minimum and required attributes, and/or provide specific minimum and required implementations/methods. With protocol extensions, we can provide a default implementation for some or all of the protocol’s methods.
Follow the agreement
Next, we’ll make our custom Person class compliant (with) Apple’s own Equatable protocol.
Following the Equatable protocol, equality can be determined using the equal operator (==), and the not equal operator (! =) to determine whether it is unequal. Most of the base types in the Swift standard library follow the Equatable protocol…
class Person : Equatable
{
var name:String
var weight:Int
var sex:String
init(weight:Int, name:String, sex:String)
{
self.name = name
self.weight = weight
self.sex = sex
}
static func == (lhs: Person, rhs: Person) -> Bool
{
if lhs.weight == rhs.weight &&
lhs.name == rhs.name &&
lhs.sex == rhs.sex
{
return true
}
else
{
return false
}
}
}
Copy the code
Apple states that “custom types that declare that they use a particular protocol need to place the name of the protocol after the type name, separated by a colon, as part of their definition.” And that’s exactly what I did:
class Person : Equatable
Copy the code
You can think of a protocol as a convention or promise specific to a class, struct, or enum. I made my custom Person class comply with a convention through the Equatable protocol, and the Person class *** * promises to implement the convention through methods or member variables required by the real Equatable protocol.
The Equatable protocol *** doesn’t implement anything ***. It simply specifies that *** must implement *** methods and/or member variables that take (follow) the *** Equatable protocol class, struct, or enum ***. There are some protocols that provide functionality through extensions, which we’ll discuss later. I won’t spend too much time talking about the POP usage of enum. I’ll leave it as an exercise for you.
Define the agreement
The best way to understand protocols is by example. I’ll build an Equatable myself to show you how the protocol works:
protocol IsEqual { static func == (lhs: Self, rhs: Self) -> Bool static func ! = (lhs: Self, rhs: Self) -> Bool }Copy the code
Keep in mind that my “IsEqual” protocol does not apply to == and! The = operator. “IsEqual” requires adherents of the protocol to implement their own *** == and! = operator.
All the rules that define protocol properties and methods are summarized in Apple’s Swift documentation. For example, never use the let keyword to define attributes in a protocol. The read-only attribute specifies the use of the var keyword, followed by {get} alone. If a method changes one or more properties, you need to mark it as mutating. You need to know why I’m rewriting == and! The = operator is defined as static. If you don’t know, it’s a good exercise to find out why.
To show you the wide applicability of a protocol like my IsEqual (or Equatable), we’ll use it to build a class below. But before we start, let’s talk about reference types versus value types.
Reference types and value types
Before continuing, you should read Apple’s article on “values and reference types.” It will get you thinking about reference types and value types. I’m purposely not going into too much detail here, because I want you to think about and understand this very important concept. It’s so important that a discussion of POP and reference/value types appears in these places at the same time:
- “Protocol-oriented Programming in Swift” by WWDC 2015
- WWDC 2015 “Building Better Apps with Value Types in Swift”
- Protocol and Value Oriented Programming in UIKit Apps
I’ll give you a hint and homework… Suppose you have multiple references to the same class instance to modify or “change” properties. These references point to the same chunk of data, so it’s not an exaggeration to call it “shared” data. In some cases, sharing data can cause problems, as shown in the following example. Does this mean we need to change all of our code to value types? ** is not! As one Apple engineer pointed out, “For example, take Windows. What does it mean to copy a Window?” Take a look at the code below and think about it.
Reference types
The following snippet from Xcode Playground presents an interesting challenge when creating a copy of the object and then changing its properties. Can you find the problem? We will discuss this in the next article.
This code also demonstrates the definition of the protocol and extension.
// Reference types: everyone has been using classes for a long time // -- think of all the implicit copying that goes on in COCOA. protocol ObjectThatFlies { var flightTerminology: String {get} func fly() } extension ObjectThatFlies {func fly() -> Void {let myType = String(describing: type(of: self)) let flightTerminologyForType = myType + " " + flightTerminology + "\n" print(flightTerminologyForType) } } class Bird : ObjectThatFlies { var flightTerminology: String = "flies WITH feathers, and flaps wings differently than bats" } class Bat : ObjectThatFlies { var flightTerminology: String = "flies WITHOUT feathers, Let bat = bat () bat. Fly () // "bat flies WITHOUT feathers," and flaps wings differently than birds" let bird = Bird() bird.fly() // "Bird flies WITH feathers, and flaps wings differently than bats" var batCopy = bat batCopy.fly() // "Bird flies WITH feathers, And flaps wings differently than bats "batCopy flightTerminology =" "batCopy. Fly () / / console output" Bat "Bat. Fly () / / console output "Bat"Copy the code
Console output from the previous code snippet
Bat flies WITHOUT feathers, and flaps wings differently than birds
Bird flies WITH feathers, and flaps wings differently than bats
Bird flies WITH feathers, and flaps wings differently than bats
Bat
Bat
Copy the code
Value types
In the Swift snippet that follows, we use struct instead of class. Here, the code looks more secure, and Apple seems to be promoting value types and POP. Note that they haven’t given up on Class yet.
// This is the beginning of a paradigm shift, not just in protocol, but also in the value type protocol ObjectThatFlies {var flightTerminology: String {get} func fly() } extension ObjectThatFlies {func fly() -> Void {let myType = String(describing: type(of: self)) let flightTerminologyForType = myType + " " + flightTerminology + "\n" print(flightTerminologyForType) } } struct Bird : ObjectThatFlies { var flightTerminology: String = "flies WITH feathers, and flaps wings differently than bats" } struct Bat : ObjectThatFlies { var flightTerminology: String = "flies WITHOUT feathers, // "bat flies WITHOUT feathers," // let bat = bat () bat. Fly () // "bat flies WITHOUT feathers," and flaps wings differently than birds" let bird = Bird() bird.fly() // "Bird flies WITH feathers, and flaps wings differently than bats" var batCopy = bat batCopy.fly() // "Bird flies WITH feathers, And flaps wings differently than bats "/ / I'm here to do is obvious batCopy Bat instance. FlightTerminology =" "batCopy. Fly () / / console output "Bat" // However, because we are using the value type, the raw data of the Bat instance has not been tampered with as a result of the previous operation. bat.fly() // "Bat flies WITHOUT feathers, and flaps wings differently than birds"Copy the code
Console output from the previous code snippet
Bat flies WITHOUT feathers, and flaps wings differently than birds
Bird flies WITH feathers, and flaps wings differently than bats
Bat flies WITHOUT feathers, and flaps wings differently than birds
Bat
Bat flies WITHOUT feathers, and flaps wings differently than birds
Copy the code
The sample code
I wrote some protocol oriented code. Read through the code, read the inline comments, read the accompanying article, follow my hyperlinks, and fully understand what I’m doing. You will use it in your next article on POP.
Use multiple protocols
When I first started writing this article, I was greedy and wanted to customize a protocol that would *** * both *** Apple’s built-in protocols Equatable and Comparable:
protocol IsEqualAndComparable { static func == (lhs: Self, rhs: Self) -> Bool static func ! = (lhs: Self, rhs: Self) -> Bool static func > (lhs: Self, rhs: Self) -> Bool static func < (lhs: Self, rhs: Self) -> Bool static func >= (lhs: Self, rhs: Self) -> Bool static func <= (lhs: Self, rhs: Self) -> Bool }Copy the code
I realized I should keep them separate to make my code as flexible as possible. Why not? Apple declares that the same class, structure, and enumeration can follow multiple protocols, as we’ll see next. Here are the two agreements I propose:
protocol IsEqual { static func == (lhs: Self, rhs: Self) -> Bool static func ! = (lhs: Self, rhs: Self) -> Bool } protocol Comparable { static func > (lhs: Self, rhs: Self) -> Bool static func < (lhs: Self, rhs: Self) -> Bool static func >= (lhs: Self, rhs: Self) -> Bool static func <= (lhs: Self, rhs: Self) -> Bool }Copy the code
Remember your algorithm
One important skill you’ll need to hone is programming algorithms and turning them into code. I guarantee that at some point in the future someone will give you a verbal description of a complex process and ask you to code it. There is often a big difference between describing certain steps in human language and then implementing them in software. I realized this when I wanted to apply IsEqual and Comparable to classes that represent lines (vectors). I remember calculating the length of a line is based on the Pythagorean theorem (see here and here) and using ==,! For vectors. =, <, >, <=, and >= the length of the line is required for comparison. My Line class will come in handy, for example, in a drawing application or game where the user clicks on two locations on the screen to create a Line between two points.
Custom classes use multiple protocols
This is my Line class, which uses two protocols, IsEqual and Comparable (below). This is a form of multiple inheritance!
class Line : IsEqual, Comparable { var beginPoint:CGPoint var endPoint:CGPoint init() { beginPoint = CGPoint(x: 0, y: 0); endPoint = CGPoint(x: 0, y: 0); } init(beginPoint:CGPoint, endPoint:CGPoint) { self.beginPoint = CGPoint( x: beginPoint.x, y: Beginpoint.y) self.endPoint = CGPoint(x: endpoint.x, y: endpoint.y)} // Line length is calculated based on the Pythagorean theorem. func length () -> CGFloat { let length = sqrt( pow(endPoint.x - beginPoint.x, 2) + pow(endPoint.y - beginPoint.y, 2) ) return length } static func == (leftHandSideLine: Line, rightHandSideLine: Line) -> Bool { return (leftHandSideLine.length() == rightHandSideLine.length()) } static func ! = (leftHandSideLine: Line, rightHandSideLine: Line) -> Bool { return (leftHandSideLine.length() ! = rightHandSideLine.length()) } static func > (leftHandSideLine: Line, rightHandSideLine: Line) -> Bool { return (leftHandSideLine.length() > rightHandSideLine.length()) } static func < (leftHandSideLine: Line, rightHandSideLine: Line) -> Bool { return (leftHandSideLine.length() < rightHandSideLine.length()) } static func >= (leftHandSideLine: Line, rightHandSideLine: Line) -> Bool { return (leftHandSideLine.length() >= rightHandSideLine.length()) } static func <= (leftHandSideLine: Line, rightHandSideLine: Line) -> Bool {return (lefthAndSideline.length () <= righthAndSideline.length ())}Copy the code
Verify your algorithm
I used the spreadsheet software Apple Numbers and prepared the visual representation of two vectors. I did some basic tests on the length() method of Line class:
Here is the test code I wrote based on the points in the diagram above:
let x1 = CGPoint(x: 0, y: 0) let y1 = CGPoint(x: 2, y: 2) let line1 = Line(beginPoint: x1, endPoint: Y1) line1.length() // returns 2.82842712474619 let x2 = CGPoint(x: 3, y: 2) let y2 = CGPoint(x: 5, y: 2) 4) let line2 = Line(beginPoint: x2, endPoint: Y2) line2.length() // returns 2.82842712474619 line1 == line2 // returns true line1! = line2 // returns false line1 > line2 // returns false line1 <= line2 // returns trueCopy the code
Test/prototype the UI using the Xcode “Single View” playground template
Did you know you could use the Xcode 9 Single View Playground template to prototype and test the user interface (UI)? It’s great – a tool for saving a lot of time and prototyping quickly. To better test my Line class, I created this playground. Homework: Before I explain, I want you to try it on yourself. I’ll show you my playground code, emulator output, and my Swift test statement.
Here is my playground code:
import UIKit import PlaygroundSupport class LineDrawingView: UIView { override func draw(_ rect: CGRect) { let currGraphicsContext = UIGraphicsGetCurrentContext() currGraphicsContext? CurrGraphicsContext setLineWidth (2.0)? .setStrokeColor(UIColor.blue.cgColor) currGraphicsContext? .move(to: CGPoint(x: 40, y: 400)) currGraphicsContext? .addLine(to: CGPoint(x: 320, y: 40)) currGraphicsContext? .strokePath() currGraphicsContext? CurrGraphicsContext setLineWidth (4.0)? .setStrokeColor(UIColor.red.cgColor) currGraphicsContext? .move(to: CGPoint(x: 40, y: 400)) currGraphicsContext? .addLine(to: CGPoint(x: 320, y: 60)) currGraphicsContext? .strokePath() currGraphicsContext? CurrGraphicsContext setLineWidth (6.0)? .setStrokeColor(UIColor.green.cgColor) currGraphicsContext? .move(to: CGPoint(x: 40, y: 400)) currGraphicsContext? .addLine(to: CGPoint(x: 250, y: 80)) currGraphicsContext? .strokePath() } } class MyViewController : UIViewController { override func loadView() { let view = LineDrawingView() view.backgroundColor = .white self.view = View}} / / in real-time view window displays the view controller PlaygroundPage. Current. LiveView = MyViewController ()Copy the code
Here’s my view output on playground simulator:
Here is the Swift code that tests my instance of Line type to match the vector I drew on our playground:
let xxBlue = CGPoint(x: 40, y: 400) let yyBlue = CGPoint(x: 320, y: 40) let lineBlue = Line(beginPoint: xxBlue, endPoint: yyBlue) let xxRed = CGPoint(x: 40, y: 400) let yyRed = CGPoint(x: 320, y: 60) let lineRed = Line(beginPoint: xxRed, endPoint: yyRed) lineRed. Length () // returns 440.454310910905 lineBlue! = lineRed // returns true lineBlue > lineRed // returns true lineBlue >= lineRed // returns true let xxGreen = CGPoint(x: 40, y: 400) let yyGreen = CGPoint(x: 250, y: 80) let lineGreen = Line(beginPoint: xxGreen, endPoint: YyGreen) linegreen.length () // returns 382.753184180093 lineGreen < lineBlue // Returns true lineGreen <= lineRed // returns true lineGreen > lineBlue // returns false lineGreen >= lineBlue // returns false lineGreen == lineGreen // returns trueCopy the code
conclusion
I hope you enjoyed today’s post and look forward to reading “Part 2” of this article. Remember, we’ll delve into advanced applications that use POP, generic protocols, the motivations behind everything from reference types to value types, enumerate the pros and cons of POP, enumerate the pros and cons of OOP, compare OOP and POP, determine why “Swift is protocol oriented,” and delve into a concept called “local reasoning.”
This article is translated by SwiftGG translation team and has been authorized to be translated by the authors. Please visit swift.gg for the latest articles.