Swift 中的Protocol

2019-12-13  本文已影响0人  张小西的BUG

Swift的底层库是由协议构成的,无疑POP是设计底层模块的核心思想,绝大多数类都可以由Struct+protocol代替。(其实类也是结构体,不过里面有很多指针记录内存地址包括强引用关系,方法列表,扩展方法列表,父类指针,具体的可以去学习OC中的Runtime都有介绍,所有访问都是通过指针去查找,但是结构体是这直接去内存中查找,所以Swift运行速度会比OC快)
Swift中的Protocol更像是一个类,申明属性和方法,并可以自己实现方法,这个是违背了OOP中抽象类的概念(CoreAnimation框架就是OOP设计,我们使用的核心动画类的父类都是CAAnimation还有Operation,他们都是抽象类不能直接使用)


最初使用Swift是非常不习惯的,Foundation框架下的三个大类都改成了用Struct去实现,各种API各种别扭,虽然可以转成OC的对象,但是这样就失去了设计的初衷意义。但是呢改用Struct去实现,有了更多高阶API,主要体现在函数式编程思想(可以参考SnapKit/Masonry这个框架去了解),还有响应式编程(GitHub上有Rswift,但是这个我不是很熟悉),举几个常用的高阶函数

func firstIndex(where predicate: (Element) throws -> Bool) rethrows -> Int?
func flatMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]

这都是数组里面的API,当然我们可以简单的来看看是怎么实现的,举一个排序的列子

extension Array where Array.Element : Comparable{
 func customerSort1(sortBlock: ( _ object1: Element , _ object2:   Element) -> Bool) -> [Element] {
      guard self.count > 0  else {
          return []
      }
      var arr = self
      let count = self.count
      for i in 0 ..< count {
          for j in i ..< count {
              if sortBlock(arr[i] , arr[j]) {
                 arr.swapAt(j, i)
              }
          }
      }
      return arr
  }
}

很熟悉的一段冒泡排序代码,只管排序,排序规则是由外部来控制的,这就能很好的解耦(后期在项目中,模块之间的交互,在整个流程中,都是参考这种思想去完成,使得模块之间的耦合很低)。还有一个点,就是类(严格来说Array不是一个类,暂且这么的称呼)的扩展,这个是加了类型约束的就是数组里的元素是需要遵循Comparable协议的,swift中的数组需要声明类型(最好在定义的时候就去申明类型还有就是字典也是需要的,不要让编译器去推测类型,从swift2.3过来人都是泪)。
看到协议的优点之一,统一接口,在数据流动中,也可以参考这种方式,但是有一个问题,就是数据在流动中被改变了,怎么办?面向值编程,我们应该遵循single flow,结构体就是能发挥很好的作用,所以Struct + Protocol就能很完美的结合,还需要注意的是,虽然每次流动都是copy的过程,但是其实并没有真正的复制,只有当数据被改变时,才会重新开辟新的内存来存贮。

接着说Protocol中的扩展,这个大概也是比较高级的写法吧,给指定类写扩展协议。如果能留意到,swift中好多第三方框架的API,都发生了变化,不再是对象+方法了,中间多了一层,这个多的一层就是由扩展协议来实现的,比如:Kingfisher中kf.setImage,其他的基本都是,就不再举例。最大的优点就是API不会因为方法名冲突,还有就是不同的类遵循的是同一个协议,但是调用的方法是各自类的扩展API,这个举例说明,还是Kingfisher,它就只有一个KingfisherCompatible协议,然后还有就是一个协议的扩展了,看到kf了是一个associatedtype关键字修饰的表示任意类型的占位只有在使用时才会被定义类型,还有一个就是范型类Kingfisher,范型(swift很重要的概念,如果写组件的话基本都会用到范型,还可以给范型加约束)

public protocol KingfisherCompatible {
    associatedtype CompatibleType
    var kf: CompatibleType { get }
}
public extension KingfisherCompatible {
     public var kf: Kingfisher<Self> {
        return Kingfisher(self)
     }
}
public final class Kingfisher<Base> {
     public let base: Base
     public init(_ base: Base) {
        self.base = base
    }

}
再接着就是遵循协议了看看Kingfisher里面,不同的类,遵循的同一个协议:

extension ImageView: KingfisherCompatible { }
extension Button: KingfisherCompatible { }

接下来就是扩展了,应该说是给指定的类写扩展

extension Kingfisher where Base: ImageView {}
extension Kingfisher where Base == String {}

有点蒙了,我们是在给Kingfisher这个类在写扩展,但是这个类初始化的类型也就是Base,是指定的类型(并且遵循了KingfisherCompatible协议),然后Base确定了kf的类型,再就是愉快的写扩展了,这样写扩展感觉是不是很高级。

是不是给所有的协议写指定类的扩展都是这么写呢?当然不是的,比如你给UIViewControlle和UIVIew写扩展的时候就没有这么麻烦了。

protocol TestProtocol{}
extension TestProtocol where Self: UIViewController {
    func test() {
        debugPrint(self.view.frame)
    }
}
extension TestProtocol where Self: UIView {
    func test() {
        debugPrint(self.frame)
    }
}

目前还不清楚为啥,类可以直接写扩展,但是结构体就不行,要写一个中间层。

这样写其实除了高大尚外,还有就是符合POP编程思想,不直接给类扩充方法,而是给协议去扩充方法,当某一个流程需要用到这些功能时,指定类遵循这个协议就行,不再是以对象为中心,是以能解决问题的协议为中心,任何对象只要遵循都可以解决问题,所以很多逻辑都是写在了指定的协议中,能够很好的分离业务避免耦合。

还有协议的协议,可以看作是协议之间的继承,没啥特殊的,所以说协议其实更像一个类。但是呢其实POP写出来的代码量,比OOP多很多,形象点,就是POP更像是在拧麻花一样,横向扩展,复用和解耦更强大,代码量就是大了点,但是更多的需要前期将架构和模块设计好,团队开发的前期一定要沟通好,如果时接手别人的代码一定要弄清楚定义的协议,后期的开发会很顺利(swift写的框架就可以看到POP的影子,比如:Alamofire......)。OOP需要抽象然后一股脑继承再继承,虽然代码少很多,但是很难分离,很难去扩展基类。

POP中如果一个流程过于复杂,定义协议可能会很麻烦,这时候其实不推荐使用协议的协议(继承),可以尝试将业务分离,然后使用组合模式,能更好的解决问题,和OOP中的组合模式差不多。

最后就是不要弄混了,协议和代理。可以把协议理解成java中的interface,或者OC中的一个类。代理还是原来的代理,用来区分block,通知的区别,这些是对象之间用来交互的方式,但是代理的功能远不止用来解决对象之间的交互,更多的是用来解偶,在设计组件或者使用第三方框架应该很有感受,避免自己的组件或者框架被入侵。

随着各种编程的思想和模式横空出世,没有绝对的好坏。倒是觉得是业务决定划分的模块的复杂程度,模块的复杂程度决定你的代码结构,代码结构决定你使用的编程方式。不管是OOP还是POP,或者MVC,MVVC还是MVP,只要能优雅的解决问题就是最好的。

上一篇下一篇

猜你喜欢

热点阅读