刘毅的技术博客

记录自己的学习生活点滴,也希望和大家交流分享!

Swift by Tutorials--Enums and Switch Statements

enum枚举类型是很多编程语言的基本特性,一般是用来存储一组表示不同type的值,比如UILabel使用的NSTextAlignment,会有.Center,.Left多个type。而在Swift中,enum除了原始的用法,它的用法更像class或struct,enum可以拥有自己的方法,甚至是构造方法,然后通过配合Switch,可以实现更灵活的代码流控制,所以才会专门拿出来讲解,足见enum+switch在Swift的重要性。

Basic enumerations

先看一个Swift中定义的简单enum,如下:

1
2
3
4
5
6
enum Shape {
    case Rectangle
    case Square
    case Triangle
    case Circle
}

而使用时,可以按照下面两种方式赋值,如果事先定义了类型,那么可以不用在后面写类型,这还是Swift type inference的表现。

1
2
var aShape = Shape.Triangle
var bShape: Shape = .Triangle

而在之后的再次赋值中,都不用再指定类型。

1
aShape = .Square

Raw values

在OC中,我们一般使用NS_ENUM()来定义枚举,并可以指定值的类型,也可以单独指定某个type的值。而在Swift中,也可以做到,而且更简单。

1
2
3
4
5
6
enum Shape: Int {
    case Rectangle
    case Square
    case Triangle
    case Circle
}

使用上述代码就指定了enum的原始值类型为Int,同时该enum获得了一个property,rawValue,用于读取当前枚举值的raw value,和一个新的初始化方法,init(rawValue:),用raw value来初始化enum。

1
2
3
var triangle = Shape.Triangle
triangle.rawValue
var square = Shape(rawValue: 1)

有一点需要注意,就是通过init(rawValue:)生成的enum值,是Optional类型的,因为可能会出现输入的raw value没有对应的enum,如下,会返回nil。

1
var notAShape = Shape(rawValue: 100)

enum可以直接给某个type赋值,然后其他未赋值的type会自动加1。

1
2
3
4
5
6
enum Shape: Int {
    case Rectangle = 10
    case Square
    case Triangle
    case Circle
}

此外,enum可以使用其他类型的raw value,比如Double,Float,String,但是需要为所有type指定对应的raw value,因为除了Int,其他类型的enum是不会对没赋值的enum自动加1的。

1
2
3
4
5
6
enum Shape: String {
    case Rectangle = "Rectangle"
    case Square = "Square"
    case Triangle = "Triangle"
    case Circle = "Circle"
}

Switch statements

switch与enum的配合在很多语言都是常见的用法,Swift中一样可以,而且有很多实用的改进。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
enum Shape {
    case Rectangle
    case Square
    case Triangle
    case Circle
}

var aShape = Shape.Rectangle

switch(aShape) {
case .Rectangle:
    print("a rectangle")
case .Square:
    print("a square")
default:
    print("other shape")
}

之前在控制流的章节我们介绍过Swift中的switch,它是需要所有case全部覆盖的,如果你实现了全部case的覆盖,则不用添加default,反之,一定要添加defaut处理。而且,你不需要在每个case后添加break,系统会自动为你添加。

1
2
3
4
5
6
7
8
switch(aShape) {
case .Rectangle, .Square:
    print("a quadrilateral")
case .Circle:
    print("a circle")
default:
    break
}

一个case处理多个值也很简单,只需要逗号隔开多个值即可,并不需要其他语言中,用不加break的多行处理。

Associated values

Associated values是Swift与其他语言的enum类型最大的不同之处,你可以向每个type添加一个类似元组类型的附加值,来作为二级判断条件,下面是有附带值的enum声明,可以为参数添加参数名。

1
2
3
4
5
6
enum Shape {
    case Rectangle(width: Float, height: Float)
    case Square(side: Float)
    case Triangle(base: Float, height: Float)
    case Circle(radius: Float)
}

使用时,可以像类或结构体初始化一样赋值,但记住不能像两者一样用.来访问。

1
var rectangle = Shape.Rectangle(width: 5, height: 10)

你可以使用这些associated values的场景只能是在switch语句中,同时你还可以使用where关键字,为case添加二级条件。

1
2
3
4
5
6
7
8
switch (rectangle) {
case .Rectangle(let width, let height) where width <= 10:
    print("Narrow rectangle:\(width)*\(height)")
case .Rectangle(let width, let height):
    print("Wide rectangle:\(width)*\(height)")
default:
    print("other shape")
}

需要注意的是,判断条件越来越复杂,便会出现符合多个case的情况,但像前面所说,系统会自动在case后添加break,所以也只会执行最先符合的case,这需要注意。

Eunms as types

Swift中的enum还有另外一个重要特性,就是可以添加方法,配合associated value,可以封转一些方便的方法,比如为上面的Shape添加一个计算面积的方法。

1
2
3
4
5
6
7
8
9
10
11
12
func area() -> Float {
    switch(self) {
    case .Rectangle(let width, let height):
        return width * height
    case .Square(let side):
        return side * side
    case .Triangle(let base, let height):
        return 0.5 * base * height
    case .Circle(let radius):
        return Float(M_PI) * powf(radius, 2)
    }
}

然后申明一个Shape的变量,就可以直接调用该方法计算面积了。

1
2
var circle = Shape.Circle(radius: 5)
circle.area()

除此之外,可以添加新的构建方法,这是Shape实例的构造方法,所以切记一定要给self赋值,否则self没有值是无法返回对象的。

1
2
3
4
5
6
7
8
9
init(_ rect: CGRect) {
    let width = Float(CGRectGetWidth(rect))
    let height = Float(CGRectGetHeight(rect))
    if width == height {
        self = Square(side: width)
    } else {
        self = Rectangle(width: width, height: height)
    }
}
1
var shape = Shape(CGRectMake(0, 0, 5, 10))

所以如下这个构建方法是不合法的,因为会存在无法给self赋值的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
init(_ string: String) {
    switch(string) {
    case "rectangle":
        self = Rectangle(width: 5, height: 10)
    case "square":
        self = Square(side: 5)
    case "triangle":
        self = Triangle(base: 5, height: 10)
    case "circle":
        self = Circle(radius: 5)
    default:
        break
    }
}

但是想通过这种思路来初始化Shape,也可以实现,就是构建一个static的工厂方法,这样就不存在一定要有值的限制了,可以返回Optional类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static func fromString(string: String) -> Shape? {
switch(string) {
    case "rectangle":
        return Rectangle(width: 5, height: 10)
    case "square":
        return Square(side: 5)
    case "triangle":
        return Triangle(base: 5, height: 10)
    case "circle":
        return Circle(radius: 5)
    default:
        return nil
    }
}

对应的,使用时也要用if来拆包。

1
2
3
if let anotherShape = Shape.fromString("rectangle") {
    anotherShape.area()
}

Optionals are enums

Swift的Optional类型是enum类型,下面是部分实现:

1
2
3
4
5
6
7
enum Optional<T> : NilLiteralConvertible {
  case None
  case Some(T)
  init()
  init(_ some: T)
  static func convertFromNilLiteral() -> T?
}

这里使用了泛型,并将该类型作为Some的associated value,可以使Optional持有任何类型的值。

同时enum可以实现协议,NilLiteralConvertible协议使你可以用nil来替代optional,编译器自动会将赋值nil时去调用convertFromNilLiteral,最终转化为.None。正是因为实现了这个协议,才可以对optional类型赋nil值,不然会报错。

JSON parsing using enums

本节用解析JSON这一常见用例,来实践enum在Swift中的使用。

Parsing JSON the hard way

这是常规的解析方法,看起来确实比较繁琐(但相比OC是还是简单多了)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let json = "{\"success\":true,\"data\":{\"numbers\":[1,2,3,4,5],\"animal\":\"dog\"}}"

if let jsonData = (json as NSString).dataUsingEncoding(NSUTF8StringEncoding) {
  let parsed: AnyObject? = NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions(0), error: nil)

  // Actual JSON parsing section
  if let parsed = parsed as? [String:AnyObject] {
    if let success = parsed["success"] as? NSNumber {
      if success.boolValue == true {
        if let data = parsed["data"] as? NSDictionary {
          if let numbers = data["numbers"] as? NSArray {
            print(numbers)
          }
          if let animal = data["animal"] as? NSString {
            print(animal)
          }
        }
      }
    }
  }
}

Introducing JSON.swift

这个文件之前在第四章解析JSON时用过,下面来分析下它的结构,首先是一个enum定义,因为JSON文件就是一些基本元素和dictionary或array的组合,所以使用enum和associated value来定义JSON的基本对象,这是这一解决方案的核心思想。

1
2
3
4
5
6
7
8
enum JSONValue {
  case JSONObject([String:JSONValue])
  case JSONArray([JSONValue])
  case JSONString(String)
  case JSONNumber(NSNumber)
  case JSONBool(Bool)
  case JSONNull
}

接下来为了获取.JSONObject和.JSONArray,我们利用Swift中方便添加角标访问的方式,使用subscript技术。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
subscript(i: Int) -> JSONValue? {
  get {
    switch self {
    case .JSONArray(let value):
      return value[i]
    default:
      return nil
    }
  }
}

subscript(key: String) -> JSONValue? {
  get {
    switch self {
    case .JSONObject(let value):
      return value[key]
    default:
      return nil
    }
  }
}

那么访问一般元素时呢?我们采用了computed properties来访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
var object: [String:JSONValue]? {
  switch self {
  case .JSONObject(let value):
    return value
  default:
    return nil
  }
}

var array: [JSONValue]? {
  switch self {
  case .JSONArray(let value):
    return value
  default:
    return nil
  }
}

var string: String? {
  switch self {
  case .JSONString(let value):
    return value
  default:
    return nil
  }
}

var integer: Int? {
  switch self {
  case .JSONNumber(let value):
    return value.integerValue
  default:
    return nil
  }
}

var double: Double? {
  switch self {
  case .JSONNumber(let value):
    return value.doubleValue
  default:
    return nil
  }
}

var bool: Bool? {
  switch self {
  case .JSONBool(let value):
    return value
  case .JSONNumber(let value):
    return value.boolValue
  default:
    return nil
  }
}

最后将对象转化为JSONValue也需要一个方法,而且是递归调用的,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
static func fromObject(object: AnyObject) -> JSONValue? {
  switch object {
  case let value as NSString:
    return JSONValue.JSONString(value)
  case let value as NSNumber:
    return JSONValue.JSONNumber(value)
  case let value as NSNull:
    return JSONValue.JSONNull
  case let value as NSDictionary:
    var jsonObject: [String:JSONValue] = [:]
    for (k: AnyObject, v: AnyObject) in value {
      if let k = k as? NSString {
        if let v = JSONValue.fromObject(v) {
          jsonObject[k] = v
        } else {
          return nil
        }
      }
    }
    return JSONValue.JSONObject(jsonObject)
  case let value as NSArray:
    var jsonArray: [JSONValue] = []
    for v in value {
      if let v = JSONValue.fromObject(v) {
        jsonArray.append(v)
      } else {
        return nil
      }
    }
    return JSONValue.JSONArray(jsonArray)
  default:
    return nil
  }
}

Putting it into practice

现在让我们使用JSON.swift来完成JSON的解析,进过比较,现在只需要两层嵌套就完成了原来五层的嵌套解析,而且由于使用了Optional类型,也增加了安全性。所以在Swift中一定要注重利用enum这些新特性,它非常适用于可以预定义为一组不同的子类型的类型,就像JSON。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let json = "{\"success\":true,\"data\":{\"numbers\":[1,2,3,4,5],\"animal\":\"dog\"}}"

if let jsonData = (json as NSString).dataUsingEncoding(NSUTF8StringEncoding) {
  if let parsed: AnyObject = NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions(0), error: nil) {
    if let jsonParsed = JSONValue.fromObject(parsed) {

      // Actual JSON parsing section
      if jsonParsed["success"]?.bool == true {
        if let numbers = jsonParsed["data"]?["numbers"]?.array {
          print(numbers)
        }
        if let animal = jsonParsed["data"]?["animal"]?.string {
          print(animal)
        }
      }
    }
  }
}
6vvqnj09Z6