本篇目标
掌握 Swift 枚举的定义、关联值与原始值,理解协议的声明、遵循与扩展,学会用协议实现面向协议编程。
一、枚举基础
枚举用于定义一组相关的值。
enum Direction {
case north
case south
case east
case west
}
let dir = Direction.north简化写法:
enum Direction {
case north, south, east, west
}使用枚举:
var direction = Direction.north
switch direction {
case .north:
print("向北")
case .south:
print("向南")
case .east:
print("向东")
case .west:
print("向西")
}特点:
枚举是值类型。
枚举的每个分支都是确定的值。
switch 匹配枚举必须穷举,或加 default。
二、原始值
枚举可以有一个原始值,类型可以是 String、Int、Double、Character 等。
enum Weekday: Int {
case monday = 1
case tuesday
case wednesday
case thursday
case friday
case saturday
case sunday
}
print(Weekday.monday.rawValue) // 1
print(Weekday.friday.rawValue) // 5自动递增:
enum Status: Int {
case pending // 0
case running // 1
case done // 2
}字符串原始值:
enum Environment: String {
case dev = "development"
case prod = "production"
}
print(Environment.dev.rawValue) // development用原始值初始化:
if let env = Environment(rawValue: "development") {
print(env) // dev
}注意:
原始值初始化返回可选类型。
原始值不能重复。
有原始值的枚举,不能有关联值。
三、关联值
枚举的每个分支可以关联不同类型和数量的值。
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
let barcode1 = Barcode.upc(8, 85909, 51226, 3)
let barcode2 = Barcode.qrCode("ABCDEFGHIJKLMNOP")使用关联值:
switch barcode1 {
case .upc(let numberSystem, let manufacturer, let product, let check):
print("UPC: \(numberSystem)-\(manufacturer)-\(product)-\(check)")
case .qrCode(let code):
print("QR: \(code)")
}简写(所有关联值都用 let):
switch barcode1 {
case let .upc(numberSystem, manufacturer, product, check):
print("UPC: \(numberSystem)-\(manufacturer)-\(product)-\(check)")
case let .qrCode(code):
print("QR: \(code)")
}应用场景:
网络请求结果:成功(数据)或失败(错误)
状态机:加载中、成功、失败
表达式:数字、字符串、变量
四、枚举的属性和方法
枚举可以定义计算属性和方法。
enum CompassPoint {
case north, south, east, west
var description: String {
switch self {
case .north: return "北"
case .south: return "南"
case .east: return "东"
case .west: return "西"
}
}
}
print(CompassPoint.north.description) // 北实例方法:
enum TrafficLight {
case red, yellow, green
func canGo() -> Bool {
return self == .green
}
}
print(TrafficLight.green.canGo()) // truemutating 方法:
enum Switch {
case off, on
mutating func toggle() {
self = self == .off ? .on : .off
}
}
var s = Switch.off
s.toggle()
print(s) // on五、CaseIterable
让枚举可以遍历所有分支。
enum Direction: CaseIterable {
case north, south, east, west
}
for dir in Direction.allCases {
print(dir)
}数量:
print(Direction.allCases.count) // 4六、协议基础
协议定义一组方法和属性的要求,遵循协议的类、结构体、枚举必须实现。
protocol Greetable {
var name: String { get }
func greet()
}
struct Person: Greetable {
var name: String
func greet() {
print("Hello, \(name)")
}
}
let p = Person(name: "Tom")
p.greet() // Hello, Tom属性要求:
{ get }:只读,可实现为只读或读写。{ get set }:读写,必须可读可写。
protocol Named {
var name: String { get set }
}方法要求:
protocol Calculable {
func add(a: Int, b: Int) -> Int
}mutating 方法要求:
protocol Resettable {
mutating func reset()
}
struct Counter: Resettable {
var count = 0
mutating func reset() {
count = 0
}
}七、协议作为类型
协议可以作为类型使用,存储任何遵循该协议的值。
protocol Shape {
func area() -> Double
}
struct Circle: Shape {
var radius: Double
func area() -> Double {
return .pi * radius * radius
}
}
struct Rectangle: Shape {
var width: Double
var height: Double
func area() -> Double {
return width * height
}
}
let shapes: [Shape] = [
Circle(radius: 1),
Rectangle(width: 2, height: 3)
]
for shape in shapes {
print(shape.area())
}用途:
数组元素类型统一
函数参数类型
属性类型
八、协议继承
协议可以继承其他协议。
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
protocol Person: Named, Aged {
func introduce()
}
struct Student: Person {
var name: String
var age: Int
func introduce() {
print("我是 \(name),今年 \(age) 岁")
}
}注意:
遵循子协议时,需要实现所有父协议的要求。
一个类型可以同时遵循多个协议。
九、协议扩展
协议可以通过扩展提供默认实现。
protocol Greetable {
var name: String { get }
func greet()
}
extension Greetable {
func greet() {
print("Hello, \(name)")
}
}
struct Person: Greetable {
var name: String
}
let p = Person(name: "Tom")
p.greet() // Hello, Tom优点:
减少重复代码。
让协议拥有默认行为。
为协议添加约束:
extension Collection where Element: Equatable {
func allEqual() -> Bool {
for element in self {
if element != first {
return false
}
}
return true
}
}
print([1, 1, 1].allEqual()) // true
print([1, 2, 1].allEqual()) // false十、协议组合
使用 & 组合多个协议。
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
func printInfo(person: Named & Aged) {
print("\(person.name),\(person.age) 岁")
}用途:
函数参数要求同时满足多个协议。
类型别名简化复杂组合。
typealias PersonInfo = Named & Aged十一、可选协议要求
用 @objc 标记的协议可以定义可选要求,仅适用于类。
@objc protocol DataSource {
func numberOfItems() -> Int
@objc optional func titleForItem(at index: Int) -> String
}
class MyDataSource: DataSource {
func numberOfItems() -> Int {
return 10
}
}注意:
可选要求只能用于
@objc协议。遵循类型必须是类。
纯 Swift 场景建议用协议扩展提供默认实现,替代可选要求。
十二、面向协议编程
Swift 提倡面向协议编程(POP),优先用协议 + 组合,而不是继承。
示例:
protocol Flyable {
func fly()
}
protocol Swimmable {
func swim()
}
struct Duck: Flyable, Swimmable {
func fly() {
print("鸭子在飞")
}
func swim() {
print("鸭子在游泳")
}
}
struct Fish: Swimmable {
func swim() {
print("鱼在游泳")
}
}优点:
灵活组合能力。
避免类的单继承限制。
便于测试和替换实现。
十三、避坑指南
枚举分支尽量少而清晰:过多分支考虑拆分。
关联值 vs 原始值:不能同时存在。
协议命名规范:表示能力用
-able、-ible,表示身份用名词。协议扩展默认实现:注意不要与具体实现冲突。
@objc 可选要求:能不用就不用,优先用协议扩展。
协议组合:类型太多时用 typealias 简化。
十四、检查清单
[ ] 能定义枚举,使用原始值
[ ] 理解关联值的用法
[ ] 会为枚举添加计算属性和方法
[ ] 会使用 CaseIterable
[ ] 能定义协议,遵循协议
[ ] 理解协议作为类型
[ ] 会使用协议扩展提供默认实现
[ ] 了解面向协议编程的思路