Swift 从入门到精通-第二篇

0 阅读5分钟

第7章:枚举与结构体 - 自定义类型

7.1 枚举 (Enum) - 限定取值范围

想象一个交通灯,只有三种状态:红、黄、绿。用枚举可以清晰表达:

enum TrafficLight {
    case red
    case yellow
    case green
}

// 使用
var light = TrafficLight.red
light = .green  // 可以简写,因为类型已知

为什么要用枚举?

  • 编译器检查:不会出现无效值
  • 代码自文档:看到 TrafficLight 就知道有哪些可能
  • Switch 必须处理所有 case,不会遗漏

带原始值的枚举

enum CompassDirection: String {
    case north = "N"
    case south = "S"
    case east = "E"
    case west = "W"
}

let direction = CompassDirection.north
print(direction.rawValue)  // "N"

// 从原始值创建(可能失败,返回可选类型)
let possibleDirection = CompassDirection(rawValue: "N")  // Optional(CompassDirection.north)
let unknownDirection = CompassDirection(rawValue: "X")   // nil

原始值类型: String、Character、Int、Float、Double

enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
// venus = 2, earth = 3... 自动递增

关联值 - 枚举的超能力

enum Barcode {
    case upc(Int, Int, Int, Int)      // 关联4个整数
    case qrCode(String)               // 关联1个字符串
}

var productBarcode = Barcode.upc(8, 85909, 51226, 3)
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")

// 用 switch 提取关联值
switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
    print("UPC: (numberSystem), (manufacturer), (product), (check)")
case .qrCode(let productCode):
    print("QR code: (productCode)")
}

// 如果所有 case 都用 let,可以提到 switch 前面
switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
    print("UPC (numberSystem)")
case let .qrCode(productCode):
    print("QR (productCode)")
}

什么时候用关联值?

  • 不同 case 需要携带不同信息
  • 比如网络请求结果:成功时带数据,失败时带错误信息
enum NetworkResult<T> {
    case success(T)
    case failure(Error)
}

递归枚举

indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
}

func evaluate(_ expression: ArithmeticExpression) -> Int {
    switch expression {
    case .number(let value):
        return value
    case .addition(let left, let right):
        return evaluate(left) + evaluate(right)
    case .multiplication(let left, let right):
        return evaluate(left) * evaluate(right)
    }
}

// (5 + 4) * 2
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let two = ArithmeticExpression.number(2)
let product = ArithmeticExpression.multiplication(sum, two)

print(evaluate(product))  // 18

indirect 关键字告诉编译器在枚举 case 中存储引用而不是值。

7.2 结构体 (Struct) - 轻量级数据容器

结构体是值类型,适合存储简单的数据:

struct Resolution {
    var width = 0
    var height = 0
}

var hd = Resolution(width: 1920, height: 1080)
var cinema = hd  // 复制一份

cinema.width = 2048

print(hd.width)      // 1920(没变!)
print(cinema.width)  // 2048

值类型的特点:

  • 赋值时会复制
  • 修改互不影响
  • 线程安全(没有共享状态)

结构体的初始化

struct Person {
    var name: String
    var age: Int
    
    // 自动生成:逐一成员初始化器
    // init(name: String, age: Int)
}

let person = Person(name: "Alice", age: 25)

Swift 自动为结构体生成初始化器,不需要手动写!

结构体的方法

struct Point {
    var x = 0.0, y = 0.0
    
    // 实例方法
    func distanceTo(_ other: Point) -> Double {
        let dx = x - other.x
        let dy = y - other.y
        return sqrt(dx * dx + dy * dy)
    }
    
    // 修改属性的方法需要 mutating 关键字
    mutating func moveBy(x deltaX: Double, y deltaY: Double) {
        x += deltaX
        y += deltaY
    }
}

var point = Point(x: 0, y: 0)
point.moveBy(x: 10, y: 10)
print(point)  // Point(x: 10.0, y: 10.0)

为什么需要 mutating?

  • 结构体是值类型,默认不可变
  • mutating 表示这个方法会修改结构体的值

计算属性

struct Rectangle {
    var width: Double
    var height: Double
    
    // 存储属性
    var area: Double {
        return width * height
    }
    
    // 带 getter 和 setter 的计算属性
    var diagonal: Double {
        get {
            return sqrt(width * width + height * height)
        }
        set {
            // newValue 是传入的新值
            let ratio = width / height
            height = newValue / sqrt(ratio * ratio + 1)
            width = height * ratio
        }
    }
}

var rect = Rectangle(width: 3, height: 4)
print(rect.area)      // 12
print(rect.diagonal)  // 5

rect.diagonal = 10    // 修改对角线,自动调整宽高
print(rect.width)     // 6
print(rect.height)    // 8

属性观察器

struct StepCounter {
    var totalSteps: Int = 0 {
        willSet {
            print("准备把 totalSteps 从 (totalSteps) 改为 (newValue)")
        }
        didSet {
            if totalSteps > oldValue {
                print("增加了 (totalSteps - oldValue) 步")
            }
        }
    }
}

var counter = StepCounter()
counter.totalSteps = 100  // 增加了 100 步
counter.totalSteps = 150  // 增加了 50 步

7.3 结构体 vs 类 - 什么时候用哪个?

特性结构体 (Struct)类 (Class)
类型值类型引用类型
继承不支持支持
多态不支持支持
内存栈(快)堆(相对慢)
线程安全天然安全需要额外处理
自动初始化器没有(必须自己写或继承)

优先使用结构体,除非你需要:

  • 继承
  • 需要多个引用指向同一个实例
  • 需要生命周期管理(deinit)

常见的结构体:

  • CGPoint、CGSize、CGRect
  • String、Array、Dictionary、Set
  • Date

第8章:类与面向对象编程

8.1 类的定义

类是引用类型,多个变量可以引用同一个实例:

class Person {
    var name: String
    var age: Int = 0
    
    // 必须写初始化器
    init(name: String) {
        self.name = name
    }
    
    func greet() {
        print("Hello, I'm (name)")
    }
}

let person1 = Person(name: "Alice")
let person2 = person1  // 引用同一个对象

person2.name = "Bob"
print(person1.name)  // Bob(变了!)

8.2 初始化器详解

指定初始化器 (Designated Initializer)

class Vehicle {
    var numberOfWheels: Int
    var color: String
    
    // 指定初始化器:负责初始化所有属性
    init(numberOfWheels: Int, color: String) {
        self.numberOfWheels = numberOfWheels
        self.color = color
    }
}

规则:

  • 必须确保所有属性都被初始化
  • 必须先初始化自己的属性,再调用父类的初始化器
  • 一个类至少有一个指定初始化器

便利初始化器 (Convenience Initializer)

class Vehicle {
    var numberOfWheels: Int
    var color: String
    
    init(numberOfWheels: Int, color: String) {
        self.numberOfWheels = numberOfWheels
        self.color = color
    }
    
    // 便利初始化器:必须调用同一个类的其他初始化器
    convenience init(color: String) {
        self.init(numberOfWheels: 4, color: color)
    }
    
    convenience init() {
        self.init(color: "Black")
    }
}

let car = Vehicle()  // 4个轮子,黑色

便利初始化器的用途:

  • 提供默认值
  • 简化调用
  • 从其他数据源创建(比如从 JSON)

8.3 继承

class Bicycle: Vehicle {
    var hasBasket: Bool
    
    init(hasBasket: Bool, color: String) {
        self.hasBasket = hasBasket
        super.init(numberOfWheels: 2, color: color)  // 调用父类初始化器
    }
}

let bike = Bicycle(hasBasket: true, color: "Red")
print(bike.numberOfWheels)  // 2

重写 (Override)

class Car: Vehicle {
    var licensePlate: String
    
    init(licensePlate: String, color: String) {
        self.licensePlate = licensePlate
        super.init(numberOfWheels: 4, color: color)
    }
    
    // 重写方法
    override func description() -> String {
        return "(super.description()) - Plate: (licensePlate)"
    }
    
    // 重写属性
    override var numberOfWheels: Int {
        didSet {
            print("Wheel count changed to (numberOfWheels)")
        }
    }
}

final 关键字:阻止重写

class Vehicle {
    final var numberOfWheels: Int  // 子类不能重写
    
    final func description() -> String {  // 子类不能重写
        return "Vehicle with (numberOfWheels) wheels"
    }
}

8.4 多态

class Animal {
    func makeSound() {
        print("Some sound")
    }
}

class Dog: Animal {
    override func makeSound() {
        print("Woof!")
    }
}

class Cat: Animal {
    override func makeSound() {
        print("Meow!")
    }
}

// 多态:同一个方法调用,不同实现
let animals: [Animal] = [Dog(), Cat(), Animal()]
for animal in animals {
    animal.makeSound()
}
// Woof!
// Meow!
// Some sound

8.5 类型检查和转换

let animals: [Animal] = [Dog(), Cat(), Dog()]

// is:检查类型
for animal in animals {
    if animal is Dog {
        print("这是一只狗")
    }
}

// as?:向下转型(可能失败,返回可选类型)
for animal in animals {
    if let dog = animal as? Dog {
        dog.fetchBall()  // Dog 特有的方法
    }
}

// as!:强制向下转型(确定成功时用)
let definitelyADog = animals[0] as! Dog

// as:向上转型(总是安全)
let animal = Dog() as Animal

8.6 析构器 (Deinitializer)

class FileHandler {
    let filename: String
    var fileHandle: FileHandle?
    
    init(filename: String) {
        self.filename = filename
        self.fileHandle = FileHandle(forReadingAtPath: filename)
        print("打开文件: (filename)")
    }
    
    deinit {
        fileHandle?.closeFile()
        print("关闭文件: (filename)")
    }
}

func processFile() {
    let handler = FileHandler(filename: "data.txt")
    // 处理文件...
}  // handler 超出作用域,自动调用 deinit

processFile()
// 打开文件: data.txt
// 关闭文件: data.txt