swift-面向协议

Posted by sunzhongliang on January 9, 2021

面向协议

面向协议(Protocol Oriented Programming, 简称POP),是swift的一种编程范式,Apple于2015年WWDC上提出,在swift的标准库中,能见到大量POP的影子,同时swift也是一门面向对象的编程语言(Object Oriented Programming, 简称OOP), 在项目开发中这两种编程规范相辅相成

回顾OOP

OOP的三大特性封装、继承、多态
继承的经典使用场合:当多个类具有很多共性(方法、属性等)时,可以将这些共性抽取到一个父类当中,最后这些类继承这个父类。
但有时候使用OOP并不能很好的解决问题,比如:

// 继承自 UIViewController
class BVC: UIViewController {
    func run() {
        print("run")
    }
}

// 继承自 UITableViewController
class DVC: UITableViewController {
    func run() {
        print("run")
    }
}

BVCDVC具有公共的方法run, 但由于是继承自不同的父类, 所以基于OOP我们可以采用:

  1. 第一种做法:
    run方法放到另外一个对象A当中,然后BVCDVC拥有对象A属性(相当于将方法抽离到对象A当中,然后BVC和DVC分别去调用这个A对象)
    弊端: 多了一些额外的依赖关系(两个类都需要依赖于对象A)

  2. 第二种做法:
    run方法增加到UIViewController扩展方法当中(因为UITableViewController继承自UIViewController, 所以可以这么干)
    弊端: 给UIViewController增加扩展方法会影响到其他所有的UIViewController的子类,但实际上run方法我只想给这两个类(BVC和DVC)使用

  3. 第三种做法:
    run方法抽取到新的父类,采用多继承ocswift不支持多继承, C++支持多继承
    弊端: 会增加程序设计复杂度,产生菱形继承等问题(内存会产生冗余)

POP的解决方案
以上问题,采用POP可完美解决问题,

// 定义协议
protocol Runnable {
    func run()
}
// 给协议增加扩展方法
extension Runnable {
    func run() {
        print("run")
    }
}
// BVC和DVC只需要遵守Runnable协议即可
class BVC: UIViewController, Runnable {
    
}

class DVC: UIViewController, Runnable {
    
}

所以面向协议编程,在某些时候也是一种很好的解决问题的方式.

利用协议实现前缀效果

在开发当中,通常都会给一些类扩展一些方法,比如假设要对一个字符串增加一个计算只包含有数字的长度:

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

var word = "1234qwer5678"
print(word.numberCount)

但这样做是对系统的String类增加了一个numberCount的方法,有冲突的风险,完美的做法是在类前面加一个前缀用来区分,直接去更改扩展的方法名也是可以的,但通过协议去扩展实现更加优雅。

struct MT {
    // 由于MT是自己定义的结构体,需要保存传入的字符串值以进行计算
    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 mt: MT {
        // 实例化MT的时候,将字符串内容传递到MT当中保存
        return MT(self)
    }
}

var word = "1234qwer5678"
// 调用方式是通过命名空间了
print(word.mt.numberCount)

以上就是通过给String类型增加了一个MT的命名空间,但假设要给数组、或者其他类型增加一个方法呢? 更优雅的实现方式是使用泛型去实现:

// 通过泛型去实现扩展方法
struct MT<Base> {
    var base: Base;
    init(_ base: Base) {
        self.base = base;
    }
}

// 给String类增加一个MT的扩展
extension String {
    var mt: MT<String> {
        // 实例化MT的时候,将字符串内容传递到MT当中保存
        return MT(self)
    }
}

// 给MT增加一个numberCount的扩展, (当Base是String的时候)
extension MT where Base == String {
    var numberCount: Int {
        var count = 0
        for c in base where ("0"..."9").contains(c) {
            count += 1
        }
        return count
    }
}
// string类
var word = "1234qwer5678"
// 通过命名空间的形式去调用
print(word.mt.numberCount) // 输出: 8


// 自定义类
class MyClass: NSObject {
    var name: String;
    var age: Int;
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}
// 给自定义类增加自定义扩展
extension MyClass {
    var mt: MT<MyClass> {
        // 实例化MT的时候,将字符串内容传递到MT当中保存
        return MT(self)
    }
}
extension MT where Base: MyClass {
    var desc: String {
        return "My name is \(base.name), \(base.age) years old"
    }
}
// 自定义的类
var person = MyClass(name: "jack ma", age: 46)
print(person.mt.desc)  // 输出: My name is jack ma, 46 years old

这样写就可以更加优雅的实现自定义前缀了,而且使用到了泛型,对于任何类都是支持的

增加方法、类方法

struct MT<Base> {
    let base: Base
    init(_ base: Base) {
        self.base = base
    }
}

protocol MTCompatible {
    
}

extension MTCompatible {
    static var mt: MT<Self>.Type {
        get { MT<Self>.self }
        set {}
    }
    var mt: MT<Self> {
        get { MT(self) }
        set {}
    }
}
extension String: MTCompatible { }
extension MT where Base == String {
    func numberCount() -> Int {
        var count = 0
        for c in base where ("0"..."9").contains(c) {
            count += 1
        }
        return count
    }
}

let num = "123qwer123"
print(num.mt.numberCount())

// 自定义类
class MyClass: NSObject {
    var name: String;
    var age: Int;
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}
class Student: MyClass {
    
}
extension MyClass: MTCompatible {
    
}
extension MT where Base: MyClass {
    func run() {
        print("run method")
    }
    static func test() {
        print("类方法")
    }
}

let myclass = MyClass(name: "jack ma", age: 46)
let student = Student(name: "grace", age: 12)
myclass.mt.run()
student.mt.run()
// 只能打印类方法
MyClass.mt.test()
Student.mt.test()

利用协议实现类型判断

比如,我们在实现一个判断数据是否是数组时候,会这么写:

func isArray(_ array: Any) -> Bool {
    return array is [Any]
    // 这样写也是可以的
    // return array is Array<Any>
}
print(isArray(2))
print(isArray("1"))
print(isArray(NSArray()))
print(isArray(NSMutableArray()))
print(isArray([1, "2"]))

在判断一个类型是否是数组类型的时候,会这么写

// 判断类型
func isArrayType(_ type: Any.Type) -> Bool {
    return type is [Any].Type
        || type is NSArray.Type
        || type is NSMutableArray.Type
}
print(isArrayType(String.self))
print(isArrayType([Any].self))
print(isArrayType(NSArray.self))
print(isArrayType(NSMutableArray.self))


但利用协议的话就非常简单了!

// 判断类型
protocol ArrayType {
    
}
extension Array: ArrayType {
    
}
extension NSArray: ArrayType {
    
}
func isArrayType(_ type: Any.Type) -> Bool {
    return type is ArrayType.Type
}

print(isArrayType(String.self))
print(isArrayType([Any].self))
print(isArrayType(NSArray.self))
print(isArrayType(NSMutableArray.self))

本文首次发布于 孙忠良 Blog, 作者 [@sunzhongliang] , 转载请保留原文链接.