44、Swift编程:模式匹配与错误处理全解析

Swift编程:模式匹配与错误处理全解析

1. 模式匹配基础

模式匹配是一种强大的编程技术,它能让代码更具可读性和简洁性。下面我们来详细了解模式匹配的相关内容。

1.1 元组模式与 switch 语句

switch 表达式中可以构建元组,每个 case 会检查取模运算的结果。下划线表示匹配任意值。例如:

print("Buzz", terminator: " ") 
// 3 
case (_, _): 
    print(i, terminator: " ") 
  } 
} 
print("")

这里使用了元组模式 (_, _) 来匹配任意值,避免了编写 switch 语句的 default 情况。 print 调用的 terminator 参数让每行以空格结尾,最后一个 print("") 添加了一个空字符串和换行符,方便后续代码从新行开始打印。

1.2 表达式模式

表达式模式使用 ~= 模式匹配运算符来比较值。当比较结果为 true 时,匹配成功。如果值的类型相同,会使用 == 相等运算符进行比较。例如:

let matched = (1...10 ~= 5) // true

这里检查整数 5 是否在范围 1...10 内,使用了 ~= 运算符。也可以用 if case 语句实现相同功能:

if case 1...10 = 5 { 
  print("In the range") 
}
1.3 重载 ~= 运算符

可以重载 ~= 运算符来提供自定义的表达式匹配行为。例如,要实现整数与数组的模式匹配:

let list = [0, 1, 2, 3] 
let integer = 2 

let isInArray = (list ~= integer) // Error! 

if case list = integer { // Error! 
  print("The integer is in the array") 
} else { 
  print("The integer is not in the array") 
}

标准库中这样写会报错,我们可以实现缺失的模式匹配器:

// 1 
func ~=(pattern: [Int], value: Int) -> Bool { 
  // 2 
  for i in pattern { 
    if i == value { 
      // 3 
      return true 
    } 
  } 
  // 4 
  return false 
}

重载后,之前的表达式模式就能正确匹配了:

let isInArray = (list ~= integer) // true 

if case list = integer { 
  print("The integer is in the array") // Printed! 
} else { 
  print("The integer is not in the array") 
}
1.4 模式匹配的关键点
  • 模式代表值的结构。
  • 模式匹配能让代码比替代的逻辑条件更具可读性。
  • 模式匹配是从枚举值中提取关联值的唯一方法。
1.5 模式匹配挑战

以下是几个模式匹配的挑战,你可以尝试解决:
- 挑战A :给定以下代码,编写一个 if 语句,若用户未满21岁则显示错误信息。

enum FormField { 
  case firstName(String) 
  case lastName(String) 
  case emailAddress(String) 
  case age(Int) 
} 
let minimumAge = 21 
let submittedAge = FormField.age(22)
  • 挑战B :使用 for 循环找出具有液态水的行星。
enum CelestialBody { 
  case star 
  case planet(liquidWater: Bool) 
  case comet 
} 

let telescopeCensus = [ 
  CelestialBody.star, 
  CelestialBody.planet(liquidWater: false), 
  CelestialBody.planet(liquidWater: true), 
  CelestialBody.planet(liquidWater: true), 
  CelestialBody.comet 
]
  • 挑战C :使用 for 循环找出1974年发行的专辑。
let queenAlbums = [ 
  ("A Night at the Opera", 1974),
  ("Sheer Heart Attack", 1974), 
  ("Jazz", 1978), 
  ("The Game", 1980) 
]
  • 挑战D :编写一个 switch 语句,打印出纪念碑位于北半球、南半球还是赤道上。
let coordinates = (lat: 192.89483, long: -68.887463)
2. 错误处理基础

所有程序员,尤其是有经验的程序员,都需要关注错误处理。错误处理是让程序优雅失败的艺术,因为我们无法完全控制代码外部的因素,如用户输入、网络连接和外部文件访问等。

2.1 可选类型的一级错误处理

在深入学习错误处理协议和块之前,先从最简单的错误处理方式——可选类型开始。

2.1.1 可失败初始化器

初始化对象时可能会失败,例如将 String 转换为 Int

let value = Int("3")          // -> Optional(3) 
let failedValue = Int("nope") // -> nil

对于自定义的原始可表示枚举类型,编译器会为其编写可失败初始化器:

enum PetFood: String { 
  case kibble, canned 
} 

let morning = PetFood.init(rawValue: "kibble")  // -> .kibble 
let snack = PetFood.init(rawValue: "fuuud!")    // -> nil

也可以创建自定义的可失败初始化器:

struct PetHouse { 
  let squareFeet: Int 

  init?(squareFeet: Int) { 
    if squareFeet < 1 { 
      return nil 
    } 
    self.squareFeet = squareFeet 
  } 
} 

let tooSmall = PetHouse(squareFeet: 0) // nil 
let house = PetHouse(squareFeet: 1)    // okay
2.1.2 可选链

在处理可选值时,有时编译器会提示添加 ! 进行强制解包,但这在大多数情况下不是好的做法。可以使用可选链来安全地访问可选属性。例如:

class Pet {
  var breed: String? 

  init(breed: String? = nil) { 
    self.breed = breed 
  } 
} 

class Person { 
  let pet: Pet 

  init(pet: Pet) { 
    self.pet = pet 
  } 
} 

let delia = Pet(breed: "pug") 
let olive = Pet() 

let janie = Person(pet: olive) 
let dogBreed = janie.pet.breed! // This is bad! Will cause a crash!

这里强制解包可能会导致程序崩溃,更好的做法是使用 if let 语句:

if let dogBreed = janie.pet.breed { 
  print("Olive is a \(dogBreed)") 
} else { 
  print("Olive's breed is unknown.") 
}

还可以使用可选链处理更复杂的数据结构:

class Toy { 

  enum Kind { 
    case ball 
    case zombie 
    case bone 
    case mouse 
  } 

  enum Sound { 
    case squeak 
    case bell 
  } 

  let kind: Kind 
  let color: String
  var sound: Sound? 

  init(kind: Kind, color: String, sound: Sound? = nil) { 
    self.kind = kind 
    self.color = color 
    self.sound = sound 
  } 
} 

class Pet { 

  enum Kind { 
    case dog 
    case cat 
    case guineaPig 
  } 

  let name: String 
  let kind: Kind 
  let favoriteToy: Toy? 

  init(name: String, kind: Kind, favoriteToy: Toy? = nil) { 
    self.name = name 
    self.kind = kind 
    self.favoriteToy = favoriteToy 
  } 
} 

class Person { 
  let pet: Pet? 

  init(pet: Pet? = nil) { 
    self.pet = pet 
  } 
}

let janie = Person(pet: Pet(name: "Delia", kind: .dog, favoriteToy: 
Toy(kind: .ball, color: "Purple", sound: .bell))) 
let tammy = Person(pet: Pet(name: "Evil Cat Overlord", kind: .cat, 
favoriteToy: Toy(kind: .mouse, color: "Orange"))) 
let felipe = Person()

if let sound = janie.pet?.favoriteToy?.sound { 
  print("Sound \(sound)") 
} else { 
  print("No sound.") 
}

可选链通过在每个可能返回 nil 的属性或方法后添加 ? ,快速遍历可选值链。如果链中的任何值为 nil ,结果也为 nil

2.1.3 map flatMap

map flatMap 可以用于处理可选值数组。例如,要创建团队成员拥有的宠物名称数组:

let team = [janie, tammy, felipe]
let petNames = team.map { $0.pet?.name }
for pet in petNames { 
  print(pet) 
}

这里的输出包含可选值和 nil ,不太友好。可以使用 flatMap 来简化结果:

let betterPetNames = team.flatMap { $0.pet?.name } 

for pet in betterPetNames { 
  print(pet) 
}

flatMap 会先进行常规的 map 操作,然后“扁平化”结果,将 [Optional<String>] 转换为 [String]

2.2 错误协议

Swift的 Error 协议是错误处理架构的基础。任何符合该协议的类型都可以用来表示错误。枚举类型特别适合实现 Error 协议,因为它们有固定的实例集合,适合表示特定的错误类型。

例如,创建一个面包店项目来学习如何使用 Error 协议抛出和处理错误:

class Pastry { 
  let flavor: String 
  var numberOnHand: Int 

  init(flavor: String, numberOnHand: Int) { 
    self.flavor = flavor 
    self.numberOnHand = numberOnHand 
  } 
} 

enum BakeryError: Error { 
  case tooFew(numberOnHand: Int) 
  case doNotSell 
  case wrongFlavor 
}
2.3 抛出错误

程序可以抛出符合 Error 协议的错误。在面包店示例中,添加一个 Bakery 类:

class Bakery { 
  var itemsForSale = [ 
    "Cookie": Pastry(flavor: "ChocolateChip", numberOnHand: 20), 
    "PopTart": Pastry(flavor: "WildBerry", numberOnHand: 13), 
    "Donut" : Pastry(flavor: "Sprinkles", numberOnHand: 24), 
    "HandPie": Pastry(flavor: "Cherry", numberOnHand: 6) 
  ] 

  func orderPastry(item: String, 
                   amountRequested: Int, 
                   flavor: String)  throws  -> Int { 
    guard let pastry = itemsForSale[item] else { 
      throw BakeryError.doNotSell 
    } 
    guard flavor == pastry.flavor else { 
      throw BakeryError.wrongFlavor 
    } 
    guard amountRequested < pastry.numberOnHand else { 
      throw BakeryError.tooFew(numberOnHand: pastry.numberOnHand) 
    }
    pastry.numberOnHand -= amountRequested 

    return pastry.numberOnHand 
  } 
}

调用 orderPastry 方法时,如果传入不支持的商品,程序会抛出错误:

let bakery = Bakery() 
bakery.orderPastry(item: "Albatross", 
                   amountRequested: 1, 
                   flavor: "AlbatrossFlavor")

这里代码会崩溃,因为没有处理抛出的错误。

2.4 处理错误

处理错误有两种方式:立即处理或向上冒泡。需要根据实际情况选择合适的方式。

2.4.1 立即处理错误

使用 do-catch 块来立即处理错误:

do { 
  try bakery.orderPastry(item: "Albatross", 
                         amountRequested: 1,
                         flavor: "AlbatrossFlavor") 
} catch BakeryError.doNotSell { 
  print("Sorry, but we don't sell albatross") 
} catch BakeryError.wrongFlavor { 
  print("Sorry, but we don't carry albatross flavor") 
} catch BakeryError.tooFew { 
  print("Sorry, we don't have enough albatross to fulfill your order") 
}

代码中可能抛出错误的部分必须放在 do 块中,使用 try 标记可能抛出错误的点。

2.4.2 不关注详细错误

可以使用 try? 来运行可能抛出错误的方法,返回一个可选类型。如果成功则设置值,否则为 nil ,无需设置 do-catch 块:

let remaining = try? bakery.orderPastry(item: "Albatross", 
                                       amountRequested: 1, 
                                 flavor: "AlbatrossFlavor")
2.4.3 错误时终止程序

如果确定代码不会失败,可以使用 fatalError

do { 
  try bakery.orderPastry(item: "Cookie", amountRequested: 1, flavor: 
"ChocolateChip") 
} 
catch { 
  fatalError() 
}

总结

通过本文,我们学习了模式匹配和错误处理的基础知识。模式匹配能让代码更简洁易读,错误处理则能让程序在面对各种意外情况时优雅失败。掌握这些技术,能让我们编写出更健壮、更可靠的Swift代码。

Swift编程:模式匹配与错误处理全解析

3. 模式匹配与错误处理的综合应用

在实际编程中,模式匹配和错误处理往往会结合使用,以实现更复杂的逻辑和更健壮的程序。下面我们通过一些示例来展示它们的综合应用。

3.1 结合模式匹配处理错误

在错误处理中,可以使用模式匹配来更精确地捕获和处理不同类型的错误。例如,在面包店示例中,我们可以使用模式匹配来处理不同的 BakeryError

do {
    try bakery.orderPastry(item: "Albatross", amountRequested: 1, flavor: "AlbatrossFlavor")
} catch let error as BakeryError {
    switch error {
    case .doNotSell:
        print("Sorry, but we don't sell albatross")
    case .wrongFlavor:
        print("Sorry, but we don't carry albatross flavor")
    case .tooFew(let numberOnHand):
        print("Sorry, we only have \(numberOnHand) albatross, not enough to fulfill your order")
    }
} catch {
    print("An unknown error occurred: \(error)")
}

在这个例子中,我们使用 switch 语句对 BakeryError 进行模式匹配,根据不同的错误类型输出不同的错误信息。同时,还添加了一个默认的 catch 块来处理未知错误。

3.2 模式匹配在错误恢复中的应用

模式匹配还可以用于错误恢复。例如,当遇到特定的错误时,可以尝试采取一些恢复措施。假设在面包店中,如果库存不足,我们可以尝试补货:

do {
    try bakery.orderPastry(item: "Donut", amountRequested: 30, flavor: "Sprinkles")
} catch BakeryError.tooFew(let numberOnHand) {
    print("We only have \(numberOnHand) donuts, trying to restock...")
    // 模拟补货操作
    bakery.itemsForSale["Donut"]?.numberOnHand += 20
    do {
        let remaining = try bakery.orderPastry(item: "Donut", amountRequested: 30, flavor: "Sprinkles")
        print("Order fulfilled! Remaining donuts: \(remaining)")
    } catch {
        print("Still can't fulfill the order after restocking: \(error)")
    }
} catch {
    print("An unknown error occurred: \(error)")
}

在这个例子中,当捕获到 BakeryError.tooFew 错误时,我们尝试补货,然后再次尝试下单。如果仍然失败,则输出相应的错误信息。

4. 最佳实践与注意事项

在使用模式匹配和错误处理时,有一些最佳实践和注意事项需要遵循,以确保代码的质量和可维护性。

4.1 模式匹配的最佳实践
  • 使用有意义的模式 :模式应该能够清晰地表达匹配的意图,避免使用过于复杂或难以理解的模式。
  • 覆盖所有可能的情况 :在使用 switch 语句进行模式匹配时,要确保覆盖所有可能的情况,避免出现未处理的情况。可以使用 default 情况来处理未知情况。
  • 结合其他控制流语句 :模式匹配可以与 if for 等控制流语句结合使用,以实现更复杂的逻辑。
4.2 错误处理的最佳实践
  • 尽早处理错误 :在代码中尽早处理可能出现的错误,避免错误传播到其他部分,导致更难调试。
  • 提供有意义的错误信息 :在抛出和处理错误时,要提供有意义的错误信息,方便调试和用户理解。
  • 选择合适的错误处理方式 :根据具体情况选择合适的错误处理方式,如立即处理、向上冒泡或忽略详细错误。
4.3 注意事项
  • 避免过度使用强制解包 :在处理可选值时,尽量避免使用强制解包,以免导致程序崩溃。可以使用可选链、 if let guard let 等安全的方式来处理可选值。
  • 注意错误类型的一致性 :在抛出和捕获错误时,要确保错误类型的一致性,避免出现类型不匹配的问题。
5. 总结与展望

通过本文的学习,我们深入了解了Swift中的模式匹配和错误处理技术。模式匹配能够让代码更加简洁、易读,提高代码的可读性和可维护性;错误处理则能够让程序在面对各种意外情况时优雅地失败,提高程序的健壮性和可靠性。

在实际编程中,我们应该充分利用模式匹配和错误处理的优势,结合最佳实践和注意事项,编写出高质量的Swift代码。同时,随着Swift语言的不断发展,模式匹配和错误处理的功能也可能会不断增强,我们需要持续关注和学习,以跟上技术的发展步伐。

以下是一个简单的流程图,展示了面包店订单处理的流程:

graph TD;
    A[用户下单] --> B{检查商品是否在售};
    B -- 是 --> C{检查口味是否正确};
    B -- 否 --> D[抛出doNotSell错误];
    C -- 是 --> E{检查库存是否足够};
    C -- 否 --> F[抛出wrongFlavor错误];
    E -- 是 --> G[处理订单,更新库存];
    E -- 否 --> H[抛出tooFew错误];
    D --> I[输出错误信息];
    F --> I;
    H --> J{尝试补货};
    J -- 成功 --> K[再次尝试处理订单];
    J -- 失败 --> I;
    K -- 成功 --> L[输出订单成功信息];
    K -- 失败 --> I;
    G --> L;

通过这个流程图,我们可以更清晰地看到面包店订单处理过程中可能出现的错误以及相应的处理方式。

总之,模式匹配和错误处理是Swift编程中非常重要的技术,掌握它们对于编写高质量的Swift代码至关重要。希望本文能够帮助你更好地理解和应用这些技术。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值