WWDC 2015 - Session 408 - Protoc

作者: NinthDay | 来源:发表于2016-03-14 16:41 被阅读630次

    由 Dave Abrahams Professor 演讲,这货是C++牛人,自行wiki。

    笔记先从 Object Oriented Programming - OOP(面向对象编程)说起,自然少不了 Class 这个主角,当初我入坑 iOS 时就问过一个问题:Class 和 Struct 的区别以及应用场景?

    当然本文中不会详尽地去比较孰优孰劣,只是简单阐述下,仅供参考。

    Classes Are Awesome

    • Encapsulation(封装)
    • Access Control(访问控制)
    • Abstraction(抽象)
    • Namespace(命名空间)
    • Expressive Syntax(表达语法)
    • Extensibility(拓展性)

    其中“Access Control”、“Abstraction”和“Namespace” 指明是 Class 令人头疼之处。

    先说说继承性,Class 完胜 Structure,记住Structure是不具有继承性的!

    类可以将对象抽象成多个描述属性和方法,可以通过继承得到子类,也就是传统意义上的superclass 和 subclass。 子类可以从父类继承一系列方法,当然也可以通过 override 重写父类的方法。

    The Three Beef

    • Implicit Sharing
    • Inheritance All Up In Your Business
    • Lost Type relationShips

    第一点:Class 实例需要在堆上分配内存,此时有两个对象A和B要操作该实例,那么传递给A和B类实例的指针(内存地址)即可,只有一个线程时不会有任何问题,凡是有个先来后到,A操作完实例后B再接手操作对象;说说多线程情况,假设A在线程1对实例进行读操作,B在线程2对实例进行写操作,来个巧合吧!假设A在读取实例中的数据量较大的数组(指向读取旧数据),而B此刻却在修改old数据,用new数据覆盖,美名其曰更新。此时意外就产生了!A宝宝心里苦啊,我只是想读取旧数据,你却马不停蹄地覆盖旧数据。所以喽,以上情况想要解决就是复制一份实例! 但是过多的copy是否会让你在操作时提心吊胆?

    第二点:Class 只能有一个父类,意味着只能从一个父类继承,而不能继承多个类。打个比方吧,现在具有类A和类B,C具有A的所有特性,所以喽Class C:A{},C是A的子类;巧了!C具有B的所有特性,所以喽Class C:B{}; 但是却没有类似Class C:A,B{}这种多继承写法。

    关于第三点,视频举了个binary Search(二分法查找) 例子:

    // 对于有序队列的一个抽象类 关于队列元素的类型可以是Int String Double 等等
    // 所以我说这是一个抽象类!
    class Ordered {
      func precedes(other: Ordered) -> Bool{
        fatalError("implement me!")
      }
    }
    // 全局函数 传入一个排序好的数组 以及要查找的key值 通过二分法搜索返回索引值
    func binarySearch(sortedKeys: [Ordered], forKey k: Ordered) -> Int {
      var lo = 0, hi = sortedKeys.count
      while hi > lo {
        let mid = lo + (hi - lo) / 2
        if sortedKeys[mid].precedes(k) { lo = mid + 1 }
        else { hi = mid }
    }
      return lo 
    }
    

    Question: 为什么不实现Ordered类中precedes方法?

    首先已经强调了Ordered是抽象类,当然这并不是站得住脚的理由,看看下面两个继承自它的子类。

    // 这是一个标签 所以包含String类型的text属性
    class Label:Ordered{
      var text:String = ""
      ...
    }
    // 这是一个数字 所以包含Double类型的value属性
    class Number : Ordered {
      var value: Double = 0
      override func precedes(other: Ordered) -> Bool {
        return value < other.value
      }
    }
    // 还有其他类型的Ordered 子类
    

    Label 严格意义上来说是一个Ordered类,同理 Number 也是,因为他们继承自 Ordered,具有其所有的特性;刚才说到你要为Ordered类实现precedes方法?请你告诉我对于传入的 Ordered 类到底是 Label 呢,还是 Number 呢,亦或是其他呢? 要知道你根本无法确定!所以我们在Ordered类中是不能实现的,而是让子类去重写实现。理解了这点继续下面的内容。

    你会注意上面的 Number 类中的 precedes 方法有点问题:

    class Number : Ordered {
      var value: Double = 0
      override func precedes(other: Ordered) -> Bool {
        // 报错!!! 很好理解,传入的 other 为 Ordered 类即可
        // 但是 Ordered 可没有 value属性! 所以我们需要进行向下(父类->子类)cast
        return value < other.value
      }
    }
    

    修改如下:

    class Number : Ordered {
      var value: Double = 0
      // 实现也有缺陷 见下
      override func precedes(other: Ordered) -> Bool {
        return value < (other as! Number).value
      }
    }
    

    这里又有一个问题,代码要求传入 Ordered 类进行处理,那么Number 和Label 严格意义上来说都是 Ordered 类(这里要理解因为它们都是Ordered的子类),所以往代码中传入 Number 是Ok的, 而传入Label类时就很有问题了, 因为向下cast会出问题(人家明明是Label ,你想要 as! 到 Number 类,绝壁失败 程序crash)。所以喽 问题多多!

    所以Abrahams提出了几个不错的抽象机制:

    • Supports value types (and classes)
    • Supports static type relationships (and dynamic dispatch) Non-monolithic
    • Supports retroactive modeling
    • Doesn’t impose instance data on models
    • Doesn’t impose initialization burdens on models
    • Makes clear what to implement

    面向协议编程

    英文 Protocol-Oriented Programming ,面向协议编程自然少不了协议。简单来说,首先将对象属性(property)和行为(behavior)抽象成实例属性和方法;将这些准则整合成协议;最后让对象遵循这个协议并实现协议中的内容即可。

    改写上面例子:

    protocol Ordered {
      func precedes(other: Ordered) -> Bool
    }
    

    现在 Ordered 是一个协议,它的准则只有一个 precedes ,因为是协议,所以不需要具体实现。

    现在我们说 Number 类是有序的,所以只需要遵循这个协议并实现要求的内容即算满足。

    protocol Ordered {
      func precedes(other: Ordered) -> Bool
    }
    // 既然是POP 而非OOP,摒弃类吧,改成Struct 值类型哦!
    struct Number : Ordered {
      var value: Double = 0
      // 去掉override 因为我们不再是重写父类的方法 而是遵循协议
      func precedes(other: Ordered) -> Bool {
        return value < (other as! Number).value
      }
    }
    

    还没完,对于传入的other类型为 Ordered 类型,早前因为我们是重写父类方法,所以类型上要保持一致,但是现在是POP,所以我们现在可以任性了!改成Number,去掉as! Number 这难看的转换。

    修改如下:

    protocol Ordered {
      func precedes(other: Ordered) -> Bool
    }
    // 既然是POP 而非OOP,摒弃类吧,改成Struct 值类型哦!
    struct Number : Ordered {
      var value: Double = 0
      // 去掉override 因为我们不再是重写父类的方法 而是遵循协议
      func precedes(other: Number) -> Bool {
        return value < other.value
      }
    }
    

    不幸地是,编译器报错“protocol requires function 'precedes' with type'(Ordered)->Bool' candidate has non-matching type '(Number)->Bool'”,不难理解,协议方法要求传入 Ordered 类型,而我们在实现时却传入 Number 类型,严格来说我们并未遵循Ordered协议。

    值得引起注意,以及值得思考和牢记的地方:将协议中的Ordered类型改写成 Self,这叫 “Self” requirement, Self 代表任何遵循这个协议的类自身,譬如 Number遵循 Ordered协议,那么实现的方法中 Self 会替换成 Number; Label 遵循Ordered协议,那么其实现的方法中 Self 会替换成 Label,以此类推。

    protocol Ordered {
      func precedes(other: Self) -> Bool
    }
    struct Number : Ordered {
      var value: Double = 0
      func precedes(other: Number) -> Bool {
        return self.value < other.value
      }
    }
    

    再来看看二分法查找全局函数的实现:

    func binarySearch(sortedKeys: [Ordered], forKey k: Ordered) -> Int {
      var lo = 0
      var hi = sortedKeys.count
      while hi > lo {
        let mid = lo + (hi - lo) / 2
        if sortedKeys[mid].precedes(k) { lo = mid + 1 }
        else { hi = mid }
    }
    return lo 
    }
    

    编译器又报错拉! “protocol 'Ordered' can only be used as a generic constraint because it has Self or associated type requirements”。 因为我们使用 Self 替换了明确的类,所以我们只能通过添加泛型约束才能解决问题。ps:出错原因并非是函数传入参数类型为Ordered协议!! 很好奇为什么使用Self 或 关联类型就只能使用泛型约束了....2016/07/09 update: 感谢way的回答,实际上这样的,尽管sortedKeysforKey k 类型都是Ordered,但这并不能保证两个变量类型保持一致,可能前者是Number,后者是 Label呢;而我们期望 xx.preceseds(yy)这种方式调用时 xx 和 yy 的类型是相同的,这也是协议明确说明的,说直白一些我们希望 xx 和 yy 的类型都是 T,但是类型 T 必须遵循(实现) Ordered 协议。这也是为什么要用泛型约束了。

    修改如下:

    func binarySearch<T : Ordered>(sortedKeys: [T], forKey k: T) -> Int {
      var lo = 0
      var hi = sortedKeys.count
      while hi > lo {
        let mid = lo + (hi - lo) / 2
        if sortedKeys[mid].precedes(k) { lo = mid + 1 }
        else { hi = mid }
    }
    return lo 
    }
    

    基于Protocol Oriented Programming 的绘图例子

    画图自然逃不了移动点,描绘线和圆弧等基本操作,所以声明一个描绘器 Renderer 合情合理吧:

    struct Renderer {
      // 以下几个基本绘图操作
      func moveTo(p: CGPoint) { print("moveTo(\(p.x), \(p.y))") }
      func lineTo(p: CGPoint) { print("lineTo(\(p.x), \(p.y))") }
      func arcAt(center: CGPoint, radius: CGFloat,
                 startAngle: CGFloat, endAngle: CGFloat) {
          print("arcAt(\(center), radius: \(radius),"
            + " startAngle: \(startAngle), endAngle: \(endAngle))")
      } 
    }
    

    接下来,思考我们要绘制的图形可不是简单的一个点或一条直线,有可能是多边形,复合图形等等。但终究逃不出“画”这个动词以及需要一个描绘器Renderer。所以喽,指定一个协议吧,它要求传入一个renderer进行绘图:

    protocol Drawable {
      func draw(renderer: Renderer)
    }
    

    接下来想想如何描绘一个多边形?貌似只要告知它起始点位置,要连接的下一个点位置就可以了吧!所以声明一个Polygon结构体(值类型)如下:

    struct Polygon : Drawable {
      func draw(renderer: Renderer) {
        // 移动到指定点
        renderer.moveTo(corners.last!)
        // 只要按顺序连接点即可
        for p in corners {
          renderer.lineTo(p)
        }
      }
      // 一系列有循序的链接点
      var corners: [CGPoint] = []
    }
    

    绘制一个圆需要知道圆心位置和半径长度即可:

    struct Circle : Drawable {
      func draw(renderer: Renderer) {
        renderer.arcAt(center, radius: radius,
          startAngle: 0.0, endAngle: twoPi)
      }
      var center: CGPoint
      var radius: CGFloat
    }
    

    不管是绘制 Polygon 还是 Circle ,仅仅只是一个图形罢了。现在我们要更进一步,告诉我要绘制的所有图形(多边形,圆形,椭圆等等),然后我将所有图形都绘制到一张图表上:

    // 依旧遵循 Drawable 协议
    struct Diagram : Drawable {
      func draw(renderer: Renderer) {
        for f in elements {
          f.draw(renderer)
        } 
      }
      // 传入要描绘的图形
      var elements: [Drawable] = []
    }
    

    Test It!

    是时候测试下了!

    var circle = Circle(center:
      CGPoint(x: 187.5, y: 333.5),
      radius: 93.75)
    var triangle = Polygon(corners: [
      CGPoint(x: 187.5, y: 427.25),
      CGPoint(x: 268.69, y: 286.625),
      CGPoint(x: 106.31, y: 286.625)])
    var diagram = Diagram(elements: [circle, triangle])
    diagram.draw(Renderer())
    /// 终端打印信息
    /*
    $ ./test
    arcAt((187.5, 333.5),
    radius: 93.75, startAngle: 0.0,
    endAngle: 6.28318530717959)
    moveTo(106.310118395209, 286.625)
    lineTo(187.5, 427.25)
    lineTo(268.689881604791, 286.625)
    lineTo(106.310118395209, 286.625)
    $
    */
    

    面向对象更进一步

    前面的 Renderer 作为结构体存在,负责移动点、描线、画圆弧等等,这是一个实例喽,但注意到这个Renderer实例中的三个基本绘图方法执行结果仅仅是打印信息,而不是真实地在屏幕上进行绘图!太桑心了,所以喽我们现在要依靠 CGContext 来进行真实操作,首先要做的抽象一个描绘器要做的工作——实际我们已经做好了,只需要将Renderer结构体改成协议即可。如下:

    protocol Renderer {
      func moveTo(p: CGPoint)
      func lineTo(p: CGPoint)
      func arcAt(center: CGPoint, radius: CGFloat,
                 startAngle: CGFloat, endAngle: CGFloat)
    }
    

    要求 CGContext 遵循Renderer协议并进行实现:

    extension CGContext : Renderer {
      // 移动点 self 即CGContext实例本身 Self 值类型本身
      func moveTo(p: CGPoint) {
        CGContextMoveToPoint(self, position.x, position.y)
      }
      // 连线
      func lineTo(p: CGPoint) {
        CGContextAddLineToPoint(self, position.x, position.y)
      }
      // 画圆弧
      func arcAt(center: CGPoint, radius: CGFloat,
                 startAngle: CGFloat, endAngle: CGFloat) {
        let arc = CGPathCreateMutable()
        CGPathAddArc(arc, nil, c.x, c.y, radius, startAngle, endAngle, true)
        CGContextAddPath(self, arc)
      }
    }
    

    效果图:

    graphic.png

    协议扩展和泛型

    声明Bubble形状(Bubble气泡,即一个圆中包含另外一个小圆):

    struct Bubble : Drawable {
      func draw(r: Renderer) {
        // 描画一个圆
        r.arcAt(center, radius: radius, startAngle: 0, endAngle: twoPi)
        // 描画另外一个圆
        r.arcAt(highlightCenter, radius: highlightRadius,
            startAngle: 0, endAngle: twoPi)
      }
    }
    

    回忆早前的Circle形状:

    struct Circle : Drawable {
      func draw(r: Renderer) {
        // 画圆
        r.arcAt(center, radius: radius, startAngle: 0.0, endAngle: twoPi)
      } 
    }
    

    画圆,画圆,画圆!! 看来我们的描绘器renderer是时候新增一个画圆操作了!

    protocol Renderer {
      func moveTo(p: CGPoint)
      func lineTo(p: CGPoint)
      // 新增方法
      func circleAt(center: CGPoint, radius: CGFloat)
      func arcAt(
        center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat)
    }
    
    // 随之而来的改动自然就是TestRenderer要实现这个方法喽
    extension TestRenderer {
      func circleAt(center: CGPoint, radius: CGFloat) {
        arcAt(center, radius: radius, startAngle: 0, endAngle: twoPi)
      }
    }
    // 当然别忘了 CGContext
    extension CGContext {
      func circleAt(center: CGPoint, radius: CGFloat) {
         arcAt(center, radius: radius, startAngle: 0, endAngle: twoPi)
      }
    }
    

    看似挺好,但是注意TestRender和 CGContext的circleAt实现仅仅是调用arcAt方法罢了,说白了就是复制代码,显然不可取。

    这时我们要用到Swift中的Protocol Extension 特性拉,为协议加上默认实现,这次我们是对Renderer协议进行Extension,加上默认的circleAt实现:

    extension Renderer {
      // 这意味着所有遵循Renderer协议的对象都具有一个circleAt默认实现方法
      func circleAt(center: CGPoint, radius: CGFloat) {
        arcAt(center, radius: radius, startAngle: 0, endAngle: twoPi)
      }
    }
    

    这样所有只要遵循了Renderer协议的对象都具有circleAt实现方法拉!

    接着我们再为 Renderer 协议Extension一个默认实现方法:rectangleAt(edges: CGRect)。ps:强调这里并未在Renderer协议中添加rectangleAt声明,而是在Extension中添加了默认实现方法,这很重要。

    现在有如下代码:

    // 对协议进行extension 是为其实现默认方法
    extension Renderer {
      func circleAt(center: CGPoint, radius: CGFloat) { ... }
      func rectangleAt(edges: CGRect) { ... }
    }
    // 对类进行extension协议 是表明遵循协议 两者有本质区别
    extension TestRenderer : Renderer {
      func circleAt(center: CGPoint, radius: CGFloat) { ... }
      func rectangleAt(edges: CGRect) { ... }
    }
    let r = TestRenderer()
    // 1 
    r.circleAt(origin, radius: 1);
    // 2
    r.rectangleAt(edges);
    
    1. 调用的自然是TestRenderer的circleAt方法
    2. 调用的自然是TestRenderer的rectangleAt方法

    现在修改let r : Renderer = TestRenderer() ,编译器只知道r是Renderer类型,而非TestRenderer,此时1、2调用的是谁的方法呢?ps:swift以后要有笔试题 这绝壁会有!

    1. 调用TestRenderer的circleAt方法,因为这在Renderer协议中是required的
    2. 调用Renderer的默认实现方法rectangleAt。

    表示现在略感迷茫。-.-! 之后补充吧。

    关于协议的遵循:

    extension CollectionType {
      public func indexOf(element: Generator.Element) -> Index? {
        for i in self.indices {
        
          // 报错信息: binary operator '==' cannot be applied two Generator.Element operands
          if self[i] == element {
            return i 
          }
        }
        return nil 
       }
    }
    

    道理我都懂,使用 '==' 操作符进行前要确认两边对象怎么样才算相等(返回true),怎样算不相等(返回false)。举例来说,Int类型数据 2 和 3 进行比较,显然不相等喽;String类型数据“PMST” 和 “PMST” 比较是相等的喽,那如果是个自定义类型MyClass的实例对象 a 和 b 进行比较呢? 怎么样才算相等? 这个比较行为需要我们来自定义对吧,我的地盘我做主!

    而这里我们是在为CollectionType 协议实现一个默认方法IndexOf,只需要加个约束即可:

    extension CollectionType where Generator.Element : Equatable {
      public func indexOf(element: Generator.Element) -> Index? {
        for i in self.indices {
          if self[i] == element {
              return i 
          }
        }
        return nil 
       }
    }
    

    先讲到这里,更多内容请见官方Video。

    Why Coding Like This? 详述Ordered一例

    Binary search 二分法不是本文的重点,因此不会这里不会详述,但是你可以简单先看下C语言实现原理

    首先认识几个问题:

    • 要使用二分法查找的数组一定是有序的!
    • 数组元素类型可以是Int,Double,String等!

    实现myBinarySearch函数,函数传入已经排序好的数组sortedKeys以及要查找的键值k,返回键值的索引值。譬如传入[2,3,4,5,6,7]key = 7,那么返回索引号为5。

    先以Int数据类型为例:

    func myBinarySearch(sortedKeys:[Int],forKey k : Int) -> Int{
        
        var lo = 0,hi = sortedKeys.count
        while hi > lo{
            let mid = lo + (hi - lo) / 2
            if sortedKeys[mid] < k {lo = mid + 1}
            else{ hi = mid}
        }
        return lo
    }
    
    myBinarySearch([2,3,4,5,6], forKey: 4) // 索引值为2 
    

    考虑到数组元素类型还有Double String等等,所以使用泛型来实现:

    func myBinarySearch<T>(sortedKeys:[T],forKey k : T) -> Int{
        
        var lo = 0,hi = sortedKeys.count
        while hi > lo{
            let mid = lo + (hi - lo) / 2
            // 报错
            if sortedKeys[mid] < k {lo = mid + 1}
            else{ hi = mid}
        }
        return lo
    }
    

    很遗憾,编译器报错了“Binary operator'< cannot ...'”,主要是传入的泛型是否具有比较性无从得知,因此出现了报错。

    我们只需要为泛型T加上Comparable约束,问题迎刃而解。

    func myBinarySearch<T:Comparable>(sortedKeys:[T],forKey k : T) -> Int{
        
        var lo = 0,hi = sortedKeys.count
        while hi > lo{
            let mid = lo + (hi - lo) / 2
            if sortedKeys[mid] < k {lo = mid + 1}
            else{ hi = mid}
        }
        return lo
    }
    
    myBinarySearch([2,3,4,5,6], forKey: 4)
    

    是时候加些面向协议的“佐料”了。有序即具有先后之分,那么对象自身(self)和其他(other)必须有个排序问题,因此协议制定了 func precedes(other:Self)->Bool协议方法,用于判断对象自身和其他对象的排序。

    protocol Ordered{
        func precedes(other:Self) -> Bool
    }
    // 报错
    func binarySearch(sortedKeys:[Ordered],forKey k: Ordered) -> Int{
        var lo = 0,hi = sortedKeys.count
        while hi > lo{
            let mid = lo + (hi - lo) / 2
            if sortedKeys[mid].precedes(k){lo = mid + 1}
            else{ hi = mid}
        }
        return lo
    }
    

    这里同样有个问题“protocol 'Ordered' can only be used as a generic constraint because it has Self or associated type requirements”,前文已经提及,改动方法是使用泛型+约束。

    现在完整代码如下:

    protocol Ordered{
        func precedes(other:Self) -> Bool
    }
    
    func binarySearch<T : Ordered>(sortedKeys:[T],forKey k: T) -> Int{
        var lo = 0,hi = sortedKeys.count
        while hi > lo{
            let mid = lo + (hi - lo) / 2
            if sortedKeys[mid].precedes(k){lo = mid + 1}
            else{ hi = mid}
        }
        return lo
    }
    

    满心欢喜去测试,结果显然很悲伤:

    // 报错cannot invoke binarySearch with an argument...
    let position = binarySearch(["2", "3", "5", "7"], forKey: "5")
    

    原因很简单,函数要求传入的参数是遵循Ordered协议的类型T数组,而String显然没有遵循Ordered协议。因此我们接下来要做的是extension String:Ordered{},显然除了String,还有Int等,一并写了?

    // 以下扩展的作用是遵循Ordered协议 
    extension Int : Ordered {
      func precedes(other: Int) -> Bool { return self < other }
    }
    extension String : Ordered {
      func precedes(other: String) -> Bool { return self < other }
    }
    // 其他...
    

    难道为每一个类型都进行extension实现吗?显然有更好的方法,还记得前文讲得协议扩展吗?为Comparable协议增加默认实现!当然Int,String类型逃脱不了遵循Ordered的命运(因为函数传入的必须是实现Ordered协议的对象),但是真的节省了很多重复的代码,不是吗?

    extension Comparable {
      func precedes(other: Self) -> Bool { return self < other }
    }
    extension Int : Ordered {}
    extension String : Ordered {}
    

    这里的亮点是为已有协议Comparable进行Extension,添加了precedes的默认实现。这也意味着所有只要遵循了Comparable协议的已有类型,同样也是遵循Ordered协议的。但是倘若你没有使用extension Int:Ordered{}方式明确告知Int类型实现Ordered协议的话,那么即使Int类型扩展中实现有precedes方法,也不能说Int类型遵循Ordered协议!

    举个例子吧:

    protocol myProtocol{
      func sayHello()
    }
    
    class MyClass{
        func sayHello(){
            print("hello")
        }
    }
    let mc = MyClass()
    mc is myProtocol // false
    

    尽管MyClass实现了协议的要求,但是它没有告知我遵循了myProtocol协议,所以很遗憾,然并卵。

    回到先前的话题:

    extension Comparable {
      func precedes(other: Self) -> Bool { return self < other }
    }
    extension Int : Ordered {}
    extension String : Ordered {}
    
    let truth = 3.14.precedes(98.6) // 编译通过
    

    Double类型遵循Comparable协议,因此也具有precedes方法!所以可以调用precedes方法,貌似这不是我们期望的!! 更糟糕的是Double并未遵循Ordered协议!因此以下调用是错误的!

    let position = binarySearch([2.0, 3.0, 5.0, 7.0], forKey: 5.0)//报错
    

    当然我们可以继续使用extension Double:Ordered{}来解决问题,但是说实话真的不咋地,是时候思考下了!

    为何要给Comparable进行extension,为何不是给Ordered协议进行呢?就像这样:

    extension Ordered {
      //报错
      func precedes(other: Self) -> Bool { return self < other }
    }
    

    什么? 无法比较selfother?加个约束呗!

    extension Ordered where Self : Comparable {
      func precedes(other: Self) -> Bool { return self < other }
    }
    extension Double:Ordered{}
    

    还记得Self吗?其实就是遵循协议的类型的placeholder罢了!

    再来看看 let truth = 3.14.precedes(98.6) 调用,已经编译报错了,确实本就不应该能够调用,去掉它吧!

    最后美化下所写的二分法代码,你可以写成两种方式

    方式一:全局函数

    func binarySearch<
      C : CollectionType where C.Index == RandomAccessIndexType,
      C.Generator.Element : Ordered
    >(sortedKeys: C, forKey k: C.Generator.Element) -> Int {
      ...
    }
    // 注意调用方式
    let pos = binarySearch([2, 3, 5, 7, 11, 13, 17], forKey: 5)
    

    方式二:扩展已有协议

    extension CollectionType where Index == RandomAccessIndexType,
    Generator.Element : Ordered {
      func binarySearch(forKey: Generator.Element) -> Int {
        ...
    } }
    // 注意调用方式
    let pos = [2, 3, 5, 7, 11, 13, 17].binarySearch(5)
    

    相比较我更喜欢后者!

    相关文章

      网友评论

      • walkingway: 『很好奇为什么使用Self 或 关联类型就只能使用泛型约束了』因为协议只要求满足合同就好,并不能保证类型一致吧
        NinthDay:@walkingway soga

      本文标题:WWDC 2015 - Session 408 - Protoc

      本文链接:https://www.haomeiwen.com/subject/hseflttx.html