05 来,自定义一个swift的subscript

作者: 八条8tiao | 来源:发表于2016-07-22 16:00 被阅读317次

    本文参考原文为Implementing Custom Subscripts in Swift,欢迎阅读原文。

    下标是一种强大的语言功能,如果使用得当,可以显著提高代码的调用的便利性和可读性。在本教程中,我们将一起在playgroud中,通过构建一个基本跳棋游戏来探索下标。通过使用下标,你可以非常容易的在棋盘上移动一个棋子。

    1、开始行动

    struct Checkerboard {
     
      enum Square: String {
        case Empty = "\u{25AA}\u{fe0f}" // Black square
        case Red = "\u{1f534}"          // Red piece
        case White = "\u{26AA}\u{fe0f}" // White piece
      }
     
      typealias Coordinate = (x: Int, y: Int)
     
      private var squares: [[Square]] = [
        [ .Empty, .Red,   .Empty, .Red,   .Empty, .Red,   .Empty, .Red   ],
        [ .Red,   .Empty, .Red,   .Empty, .Red,   .Empty, .Red,   .Empty ],
        [ .Empty, .Red,   .Empty, .Red,   .Empty, .Red,   .Empty, .Red   ],
        [ .Empty, .Empty, .Empty, .Empty, .Empty, .Empty, .Empty, .Empty ],
        [ .Empty, .Empty, .Empty, .Empty, .Empty, .Empty, .Empty, .Empty ],
        [ .White, .Empty, .White, .Empty, .White, .Empty, .White, .Empty ],
        [ .Empty, .White, .Empty, .White, .Empty, .White, .Empty, .White ],
        [ .White, .Empty, .White, .Empty, .White, .Empty, .White, .Empty ]
      ]
    }
     
    extension Checkerboard: CustomStringConvertible {
      var description: String {
        return squares.map { row in row.map { $0.rawValue }.joinWithSeparator("") }
                      .joinWithSeparator("\n") + "\n"
      }
    }
    

    我们在Checkerboard中定义了三个元素:

    • Square: 代表棋盘上的一个格子,.Empty代表一个空格子,.Red和.White分别代表的是红色和白色的棋子。

    • Coordinate: typealias一个有两个Int类型数据构成的Tuple,代表棋盘上一个格子的坐标点。
      我们使用Coordinate来访问棋盘上的一个位置。

    • squares: 是一个二维数组,用来表示棋盘上的每一个格子的状态。

    我们现在在playground下面加入以下两行代码,

    var checkerboard = Checkerboard()
    print(checkerboard)
    

    我们初始化棋盘并打印它的description属性,我们会看到如下的输出。

    2、将棋子移动到棋盘对应的位置

    我们每次移动棋子,其实是相当于改变棋盘上面格子(square)的状态,将square的状态设置为.Empty、.Red、.White三种状态之一。square的状态保存在squares中,但是根据面向对象的设计原则,我们应该尽可能的避免向外部暴露我们内实现的细节,这样才可以为我们程序修改升级保留足够的空间,所以我们将squares数组设置为private。所以我们这个时候要增加两个方法,一个用来访问square当前的状态,一个用来修改square的状态。

    我们在 Checkerboard 内部增加如下两个方法。

    func pieceAt(coordinate: Coordinate) -> Square {
      return squares[coordinate.y][coordinate.x]
    }
     
    mutating func setPieceAt(coordinate: Coordinate, to newValue: Square) {
      squares[coordinate.y][coordinate.x] = newValue
    }
    

    我们需要注意的是,我们这两方法的参数使用的Coordinate类型,而不是数组的坐标,这样可以避免暴露内部的实现,因为如果我们参数使用数组的坐标,但未来某一天我们不再使用数组作为定义棋盘的数据结构,那么我们这两个方法就难以和未来的实现相兼容了。

    现在我们可以更新square的状态了,但是我们新增的这个两个方法先得有些丑陋,他们完全不像是一个swift自有的内容,而是被我们从外部生硬的塞进来的。

    3、定义下标(Subscripts)

    我们现在来调整一下我们的代码,我们是否可以使用计算属性来重新定义这两个方法呢?很明显,是不行的,因为我们的方法需要参数,但计算属性是不可以有参数。但是我们可以使用下标(Subscripts).

    subscript(parameterList) -> ReturnType {
      get {
        // return someValue of ReturnType
      }
     
      set (newValue) {
        // set someValue of ReturnType to newValue
      }
    }
    

    下标定义的语法同时具有函数定义和计算属性定义的语法特征。

    • 首先它像一个函数的定义,有参数列表,有返回值只是用subscript关键字代替了func关键字

    • 它的方法体内更像一个计算属性的定义,包括getter和setter方法

    我们用下面的代码替换掉pieceAt和setPieceAt两个方法。

    subscript(coordinate: Coordinate) -> Square {
      get {
        return squares[coordinate.y][coordinate.x]
      }
      set {
        squares[coordinate.y][coordinate.x] = newValue
      }
    }
    

    然后我们在playground的末尾增加下面的代码,将(3,2)点的坐标设置为.White状态。

    let coordinate = (x: 3, y: 2)
    print(checkerboard[coordinate])
    checkerboard[coordinate] = .White
    print(checkerboard)
    

    我们会得到如下的输出结果。

    4、比较下标、属性和函数

    下标在一下几个方面很像计算属性:

    • 它也是由 getter 和 setter方法构成的。
    • setter方法是可选的,也就是说它既可以是可读写的,也可以是只读的。
    • 一个只读的下标,不需要显示的设置get和set状态
    • 对于setter方法,它有一个默认的newValue参数,类型与返回值类型相同
    • 尽量使下标操作的时间复杂度为O(1)

    下标与计算属相最大的不同在于,下标没有一个属性名,和重载操作符类似,下标可以覆盖swift自己的中括号[].

    下标和函数的相似之处在于有参数列表,有返回值。不同点在于:

    • 下标没有默认的外部参数名,如果你需要使用外部参数名,那么需要显示的指定;
    • 下标不能使用inout关键字,也不能使用默认参数,但可以使用可变长参数;
    • 下标不能throw错误,也就是说下标的 getter方法必须通过返回值来表示错误setter方法既不能抛出错误也不能返回错误。

    5、增加第二个下标

    下标可以被重载,因此我们可以定义多个下标,只要他们有不同的参数列表或返回值就可以了。我们在Checkerboard里面新增下面的代码。

    subscript(x: Int, y: Int) -> Square {
      get {
        return self[(x: x, y: y)]
      }
      set {
        self[(x: x, y: y)] = newValue
      }
    }
    

    我们新增的这个方法,使用二维数组的坐标,作为参数。

    现在你已经掌握了swift的下标了,尝试寻找机会在你自己的代码里面定义它吧,它一定会使用你的代码更具可读性。如果内容中有任何错误,请和我联系,谢谢。

    相关文章

      网友评论

        本文标题:05 来,自定义一个swift的subscript

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