In a previous post, I summarized my fall hiring interview experience. As a Swift developer, one of the most frequently asked questions is: What do you think are the features and advantages of Swift over other languages (or OC)? As a short-sighted little white, this problem is really do not know how to start ah. This article, also from a small perspective, will talk about Protocol and Protocol Oriented Programming in Swift. (GitHub link)

Protocol Oriented Programming is a Programming paradigm for Swift proposed by Apple at WWDC 2015. We’ll start with the basic use of protocols and finally look at the advantages of protocols in reducing code coupling

Protocol – Indicates the basic usage of a Protocol

The Swift Programming Language

Protocol Basic syntax

  1. Attribute requirements:
    • {get set} : specifies read and write attributes
    • Static/class: specifies the type attribute
  2. Method requirements:
    • Static/class: specifies a class method
    • Mutating: Requires the implementation of mutable methods (instance methods for value types in which the instance to which it belongs and the value of any property of that instance can be modified)
  3. The constructor requires:
    • In a class that follows a protocol, it must be usedrequiredKeyword, ensuring that subclasses must also provide an implementation of the constructor. (unless there is afinalModified class, you may notrequiredBecause there will be no more subclasses)

Protocol as a type

  1. As type: represents an instance that complies with the protocol (in fact, an instance that complies with the protocol)
  2. Set of protocol types:let A: [someProtocol], a collection of instances that adhere to a protocol
  3. Delegate Design pattern: Defines protocols to encapsulate functionality that needs to be delegated

The relationship between protocols

  1. Protocol inheritance: Protocols are inheritable
  2. Protocol composition: Use&Keyword to follow multiple protocols simultaneously
  3. Protocol consistency: Useis,The as?,The as!Perform consistency checks
  4. Class-exclusive protocol: Used for protocol inheritanceclassKeyword that restricts the protocol function to be inherited by the class

The optional & @objc keyword

Optional protocols: Use optional properties, functions, and the protocol itself. All options must be labeled @objc, and the protocol itself must be labeled @objc. It can only be used by objective-C classes or @objc classes

The extension of keyword

  • (used on instances) to make an existing type conform to a protocol
  • (For protocols) You can follow other protocols to increase protocol consistency
  • Provides a default implementation (for the protocol)
  • (collocationwhereAdded restrictions on protocol use

Classes – features and problems

A Class is an important element in object-oriented programming. It represents a collection of objects that share the same structure and behavior

  • Classes can do:
    • Encapsulation: Provides external interfaces, hides specific logic, and ensures high class cohesion
    • Access Control: Relies on a class modifier (such as public or private) to ensure isolation
    • Abstraction: Abstraction of similar features for modeling
    • NameSpace NameSpace: Prevents variables and functions with the same name from conflicting in different scopes
    • The Expressive Syntax is Expressive
    • Extensibility: inheritability, rewritability, etc

Problem with Classes:

Implicit Sharing:

It may result in a large number of protective copies, resulting in reduced efficiency. Race conditions may also occur, resulting in unpredictable errors. To avoid Race condition, you need to use locks, but this is more inefficient code and can lead to Dead locks.

例 句 : The Inheritance All happened.

When inheriting, the subclass will inherit all the properties of the parent class, so the subclass may be too large, too complex logic. In particular, if a parent class has stored properties, the subclass must inherit all of them and initialize them carefully to avoid damaging the logic in the parent class. If you need to override a method of a parent class, you must be careful how and when you override it.

3. Lost Type Relationships do not reflect Type Relationships:

In the figure above, the two classes (Label and Number) have the same parent (Ordered), but calling Order in Number must use forced resolution (as!). To determine the property of Other, which is neither elegant nor buggy (if Other happens to be a Label class)

Coupling or dependency

Protocol – oriented programming can reduce the coupling of code to some extent.

Coupling is a software measure that refers to the degree of dependence of information or parameters between modules in a program. High coupling leads to higher maintenance costs and reduced code reusability. Low coupling is a characteristic of well-structured programs. Low coupling programs have better readability and maintainability.

The coupling level

The figure shows the coupling degree from high to low, which can be roughly divided into five levels:

  • Also known as ill-conditioned coupling, where one module directly uses the internal data of another module.
  • Common coupling: Also known as global coupling, the coupling of modules that interact through a Common data environment. The complex program with common coupling increases with the number of coupled modules.
  • Control coupling: When a module calls another module, it passes a Control variable (such as a switch, flag, etc.), and the module to be called uses the value of the Control variable to selectively perform a function within the block.
  • Stamp coupling: Also known as data coupling, several modules share a complex data structure.
  • Data coupling: A term used to refer to modules that share Data by passing in values. Each piece of Data is basic Data and only that Data is shared (e.g. passing an integer to a function that calculates the square root).

The problem with high coupling

  • High maintenance cost: Modifying a module may cause ripple effects, and the internal logic of other modules also needs to be modified
  • Unclear structure: Because there are too many dependencies between modules, it takes more effort to combine the modules
  • Low reusability: Each module depends on too many modules, which reduces the degree of reusability

Dependency Inversion Principle

Traditionally, dependencies are created at a high level, while specific policy Settings are applied at a lower level of modules, which are implemented by inheritance. The dependency reversal principle (DIP) refers to a specific way of decoupling in which higher-level modules do not depend on the implementation details of lower-level modules. The dependencies are reversed (reversed) so that lower-level modules depend on the requirements abstraction of higher-level modules.

DIP provisions:

  • High-level modules should not depend on low-level modules; both should depend on abstract interfaces.
  • Abstract interfaces should not be implementation-dependent. Implementations, on the other hand, should rely on abstract interfaces.

Take a simple and classic example — lamps and buttons.

First during the implementation of the traditional way of dependency is created directly on the high-level object (Button), when you need to change the low level objects (Lamp), you must also change its parent class (Button), if there are multiple low-level objects inherited from the parent class (Button), then change its parent class becomes very difficult. The second figure is in line with the DIP principle. The high-level object (Button) abstracts the requirement into an abstract interface (ButtonServer), and the concrete implementation (Lamp) depends on this abstract interface. At the same time, when you need to implement multiple underlying objects, you just need to implement them differently.

Decoupling – Protocol Oriented Programming

In Protocol – oriented programming, Protocol is actually the abstract interface in DIP. As explained before, protocol oriented programming is the practice of DIP, which reduces the coupling of code to a certain extent and avoids the problems caused by excessive coupling. Explain briefly through a concrete example below:

The first is the implementation of the high-level structure, creating the EmmettBrown class, and then declaring a requirement (the travelInTime method).

// High level implementation - EmmettBrown
final class EmmettBrown {
	private let timeMachine: TimeTraveling
	init(timeMachine: TimeTraveling) {
		self.timeMachine = timeMachine
	}
	func travelInTime(time: TimeInterval) -> String {
		return timeMachine.travelInTime(time: time)
	}
}
Copy the code

A Protocol is used to define the abstract travelInTime interface that low-level implementations will rely on.

// Abstract interface - time travel
protocol TimeTraveling {
    func travelInTime(time: TimeInterval) -> String
}
Copy the code

At last, we create DeLorean class and implement TravelInTime abstract interface by following TimeTraveling protocol.

// Low level implementation - DeLorean
final class DeLorean: TimeTraveling {
	func travelInTime(time: TimeInterval) -> String {
		return "Used Flux Capacitor and travelled in time by: \(time)s"}}Copy the code

When using, you only need to create the related class to call its method.

// Usage mode
let timeMachine = DeLorean(a)let mastermind = EmmettBrown(timeMachine: timeMachine)
mastermind.travelInTime(time: -3600 * 8760)
Copy the code

Delegate – Decouples using Protocol

Delegation is a design pattern that enables a class or structure to hand off (or delegate) some of its responsibilities to an instance of another type.

A Delegate is a design pattern that represents the transfer of part of the functionality of one object to another. The delegate pattern can be used to respond to a specific action or to receive data from an external data source, regardless of the type of the external data source. In some cases, a Delegate is more loosely coupled than top-down inheritance, effectively reducing the complexity of the code.

So what’s the relationship between Deleagte and Protocol? In Swift, a Delegate is implemented based on Protocol. A Protocol is defined to encapsulate the functionality that needs to be delegated, thus ensuring that the protocol-compliant type provides the functionality.

Protocol is one of the language features of Swift, and the Delegate uses Protocol for decoupling purposes.

Delegate Example:

// Define a delegate
protocol CustomButtonDelegate: AnyObject{
    func CustomButtonDidClick(a)
}
 
class ACustomButton: UIView {...weak var delegate: ButtonDelegate?
    func didClick(a){ delegate? .CustomButtonDidClick()}}// The class that follows the delegate
class ViewController: UIViewController.CustomButtonDelegate {
    let view = ACustomButton(a)override func viewDidLoad(a) {
        super.viewDidLoad()
        ...
        view.delegate = self
    }
    func CustomButtonDidClick(a) {
        print("Delegation works!")}}Copy the code

Code instructions

As mentioned earlier, the Delegate principle is actually quite simple. The ViewController sets the ACustomButton delegate to itself, and it follows and implements the methods in the CustomButtonDelegate protocol. So when the latter calls the didClick method, it calls the CustomButtonDidClick method, which triggers the corresponding method in the former, printing a Delegation Works!

A circular reference

We noticed that we used the weak keyword when declaring the delegate. The goal is to avoid circular references. The ViewController has a view, and the view.delegate has a strong reference to the ViewController. If one of the strong references is not set to a weak reference, it will cause circular reference problems.

AnyObject

When we define a delegate, we make protocol inherit from AnyObject. This is because, in Swift, this means that this protocol can only be applied to classes (not structs and enums).

In fact, it’s ok to make the protocol not inherit from anything, so that the Delegate can be applied to classes as well as structs and enums. Since a Delegate represents an instance that adheres to this protocol, when a Delegate is applied to a class, it is a Reference type and needs to be considered for circular references, so weak is a must.

The problem with this, however, is that when a Delegate is applied to structs and enums, it is a Value type and does not require circular references and cannot be used with weak keywords. So when a Delegate is not qualified to be used only for classes, Xcode will error the weak keyword: ‘weak’ may only be applied to class and class-bound protocol types

So why use AnyObject instead of class and NSObjectProtocol? The NSObjectProtocol comes from Objective-C and is not recommended in pure Swift projects. There is no difference between class and AnyObject, and the same functionality can be achieved in Xcode, but AnyObject is officially recommended.

The resources

  • WWDC – Protocol-Oriented Programming in Swift
  • Github – OOD-Principles-In-Swift
  • The Swift Programming Language – Protocol