Protocol Oriented Programming (POP) is a Programming paradigm of Swift, proposed by Apple in WWDC in 2015. In Swift’s standard library, you can see a lot of POP.

POP and OOP

1.1. Review the OOP

Swift is also an Object Oriented Programming language (OOP).

OOP has three major features: encapsulation, inheritance, and polymorphism.

The classic use case for inheritance: When multiple classes (such as A, B, or C) have many commonalities, these commonalities can be extracted into A parent class (such as D). Finally, classes A, B, or C inherit from class D.

Some problems, such as how to extract the public method run from BVC and DVC, are not easily solved using OOP.

class BVC: UIViewController {
    func run(a) {
        print("run")}}class DVC: UITableViewController {
    func run(a) {
        print("run")}}Copy the code

1.2. OOP solutions

  1. Put the run method into another object, A, and then BVC and DVC have object A attributes;

  2. Add the run method to the UIViewController class (UITableViewController inherits from UIViewController);

  3. Extract the run method into a new parent class using multiple inheritance (C++ supports multiple inheritance).

While this solves the problem, the first approach has some additional dependencies. The second method will cause UIViewController to become bloated and affect all its other subclasses. Third, although not used in iOS development, but in C++ will increase the complexity of the program design, resulting in diamond inheritance and other issues, developers need to solve additional.

1.3. POP solution

Define a protocol and also define the public method run. Extend the protocol and implement the abstract method RUN.

protocol Runnable {
    func run(a)
}

extension Runnable {
    func run(a) {
        print("run")}}class BVC: UIViewController.Runnable {}class DVC: UITableViewController.Runnable {}Copy the code

Compared to OOP, POP’s solution is more concise. Because the protocol supports extended implementations, it is very convenient for Swift to use POP.

OOP and POP are complementary in Swift development, and neither can replace the other. POP makes up for some of OOP’s design shortcomings.

1.4. Attention points of POP

  • Create protocols in preference to parent (base) classes
  • Value types are preferred (Struct, enum) instead of reference types (class)
  • Make use of protocol extensions
  • Don’t use protocols for protocol orientation (sometimes it makes more sense to use classes)

Add a prefix

Scenario: Add the ability to compute pure numbers for strings.

Sample code:

var str = "123ttt456"
func numberCount(_ str: String) -> Int {
    var count = 0
    for c in str where ("0"."9").contains(c) {
        count + = 1
    }
    return count
}
print(numberCount(str)) // Output: 6
Copy the code

The sample code above does the basic work, but there is still a lot of work to be done.

Optimized code 1 (Protocol extension) :

extension String {
    func numberCount(a) -> Int {
        var count = 0
        for c in self where ("0"."9").contains(c) {
            count + = 1
        }
        return count
    }
}
print(str.numberCount()) // Output: 6
Copy the code

With the protocol, all strings can use this feature, and only string types can be used. But evaluating properties is more elegant than calling functions.

Optimization code 2 (calculating attributes) :

extension String {
    var numberCount: Int {
        var count = 0
        for c in self where ("0"."9").contains(c) {
            count + = 1
        }
        return count
    }
}
print(str.numberCount) // Output: 6
print("45579test12".numberCount) // Output: 7
Copy the code

The above optimization looks perfect, but there is a risk that attribute names may conflict with the system. At this point we can add a prefix to the attribute to distinguish it.

Optimized code 3 (OC style prefixed) :

extension String {
    var db_numberCount: Int {
        var count = 0
        for c in self where ("0"."9").contains(c) {
            count + = 1
        }
        return count
    }
}
print(str.db_numberCount) // Output: 6
Copy the code

Similar OC prefix approach although hot can meet the conditions, but always feel a little OC style. We can use the Swift style to prefix properties/functions.

Optimization code 4 (Swift style prefix) :

struct DB {
    var string: String
    init(_ string: String) {
        self.string = string
    }
    var numberCount: Int {
        var count = 0
        for c in string where ("0"."9").contains(c) {
            count + = 1
        }
        return count
    }
}

extension String {
    var db: DB {
        return DB(self)}}print(str.db.numberCount) // Output: 6
Copy the code

Extends a DB (custom) attribute for a string. DB is a custom structure that implements string number counting inside. Not only do you have your own namespace, but any subsequent string-related customizations can be put into the structure.

In general, we do not extend customization only for strings. Arrays, dictionaries, custom classes, and so on can be extended with new customization. But you can’t add a new type and initialization method for every type in the DB structure, and the DB structure becomes bloated. You can use generics to solve this problem.

Optimization Code 5 (General) :

struct DB<Base> {
    var base: Base
    init(_ base: Base) {
        self.base = base
    }
}

extension String {
    var db: DB<String> { DB(self)}}class Person {
    var name: String
    init(_ name: String) {
        self.name = name
    }
}
extension Person {
    var db: DB<Person> { DB(self)}}extension DB where Base= =String {
    var numberCount: Int {
        var count = 0
        for c in base where ("0"."9").contains(c) {
            count + = 1
        }
        return count
    }
}
print(str.db.numberCount) // Output: 6

extension DB where Base: Person {
    func run(a) {
        print(base.name, "running")}}let p = Person("Big rush")
print(p.db.run()) // Output: running
Copy the code

After using generics, we can extend the functions/attributes of the corresponding type for the DB of the structure, which not only isolates the types, but also achieves unified DB management.

Note: There is a difference between Base: Person and Base == Person when extending a class.

If you want to extend attributes/methods for a class, you need to add another type attribute db.

Sample code:

extension String {
    var db: DB<String> { DB(self)}static var db: DB<String>.Type { DB<String>.self}}extension DB where Base= =String {
    var numberCount: Int {
        var count = 0
        for c in base where ("0"."9").contains(c) {
            count + = 1
        }
        return count
    }
    static func test(a) {
        print("String test")}}String.db.test() // Output: String test
Copy the code

If methods are extended for different types, instance attributes and type attributes need to be defined repeatedly. At this point, you can implement these properties using a protocol, and that type requires extension methods, just by following the protocol.

Final optimized code:

// 1. Prefix type (mediation)
struct DB<Base> {
    var base: Base
    init(_ base: Base) {
        self.base = base
    }
}

// 2. Use the protocol to extend prefix attributes
protocol DBCompatible {}extension DBCompatible {
    var db: DB<Self> { 
      set{}get { DB(self)}}static var db: DB<Self>.Type { 
      set{}get { DB<Self>.self}}}// 3. Let String have db prefix attributes
extension String: DBCompatible {}// 4. Extend functionality for db prefixes (instance/type)
extension DB where Base= =String {
    var numberCount: Int {
        var count = 0
        for c in base where ("0"."9").contains(c) {
            count + = 1
        }
        return count
    }
    static func test(a) {
        print("String test")}}print(str.db.numberCount) // Output: 6
String.db.test() // Output: String test

// Extend methods for custom classes
class Person {
    var name: String
    init(_ name: String) {
        self.name = name
    }
}

extension Person: DBCompatible {}extension DB where Base: Person {
    func run(a) {
        print(base.name, "running")}}let p = Person("Big rush")
print(p.db.run()) // Output: running
Copy the code

Note: If the extension function uses mutating, the attribute must be readable and writable when extending the prefix attribute using the protocol.

Will NSString and NSMutableString also work with String extensions?

You need to extend db properties for NSString and NSMutableString, but you really only need to extend db properties for NSString, because NSMutableString inherits from NSString.

extension NSString: DBCompatible {}Copy the code

No corresponding method can be found, but extending it with an and String method would cause code duplication. We know that String, nsstrings and NSMutableString kept a ExpressibleByStringLiteral agreement, only need to modify the extension condition is ok.

extension DB where Base: ExpressibleByStringLiteral {
    var numberCount: Int {
        var count = 0
        for c in (base as! String) where ("0"."9").contains(c) {
            count + = 1
        }
        return count
    }
    static func test(a) {
        print("String test")}}Copy the code

Note that base needs to be strongly converted to String.

Third, use protocol to realize type judgment

Scenario: Determine if the instance argument passed is an array.

Sample code:

func isArray(_ value: Any) -> Bool {
    value is [Any]}print(isArray([1.2])) // Output: true
print(isArray(["1".2])) // Output: true
print(isArray(NSArray())) // Output: true
print(isArray(NSMutableArray())) // Output: true
print(isArray("123")) // Output: false
Copy the code

Scenario: Determine if the type parameter passed is an array.

Sample code:

protocol ArrayType {}extension Array: ArrayType {}extension NSArray: ArrayType {}func isArrayType(_ type: Any.Type) -> Bool {
    type is ArrayType.Type
}
print(isArrayType([Int].self)) // Output: true
print(isArrayType([Any].self)) // Output: true
print(isArrayType(NSArray.self)) // Output: true
print(isArrayType(NSMutableArray.self)) // Output: true
Copy the code

Note: [Int].type and [Any].type have no relation. A protocol is ultimately a type that has its own meta.

For more articles in this series, please pay attention to wechat official account [1024 Planet].