Swift 从入门到精通-第三篇

第9章:协议与扩展

9.1 协议 (Protocol) - 定义接口

协议定义了一组方法、属性或其他要求的蓝图:

swift 复制代码
protocol Greetable {
    var name: String { get }  // 只读属性要求
    func greet()              // 方法要求
}

protocol Describable {
    var description: String { get }
}

遵循协议:

javascript 复制代码
struct User: Greetable, Describable {
    let name: String
    let email: String
    
    // 必须实现协议要求
    func greet() {
        print("Hi, I'm (name)!")
    }
    
    var description: String {
        return "User: (name) <(email)>"
    }
}

let user = User(name: "Alice", email: "alice@example.com")
user.greet()        // Hi, I'm Alice!
print(user.description)

9.2 协议扩展 - 提供默认实现

swift 复制代码
// 在扩展中提供默认实现
extension Greetable {
    func greet() {
        print("Hello, my name is (name)")
    }
}

struct Employee: Greetable {
    let name: String
    // 不需要实现 greet(),使用默认实现
}

let employee = Employee(name: "Bob")
employee.greet()  // Hello, my name is Bob

协议扩展的强大之处:

  • 可以给协议添加默认行为
  • 遵循者可以选择使用默认实现或自定义
  • 这是"面向协议编程"的核心

9.3 协议组合

swift 复制代码
protocol Named {
    var name: String { get }
}

protocol Aged {
    var age: Int { get }
}

struct Person: Named, Aged {
    var name: String
    var age: Int
}

// 函数接受同时遵循多个协议的类型
func wishHappyBirthday(to celebrator: Named & Aged) {
    print("Happy birthday (celebrator.name), you're (celebrator.age)!")
}

let person = Person(name: "Malcolm", age: 21)
wishHappyBirthday(to: person)

9.4 带关联类型的协议

swift 复制代码
protocol Container {
    associatedtype Item  // 关联类型
    var count: Int { get }
    mutating func append(_ item: Item)
    subscript(i: Int) -> Item { get }
}

struct IntStack: Container {
    // 自动推断 Item = Int
    private var items: [Int] = []
    
    var count: Int { items.count }
    
    mutating func append(_ item: Int) {
        items.append(item)
    }
    
    subscript(i: Int) -> Int {
        return items[i]
    }
}

// 也可以显式指定
double Stack: Container {
    typealias Item = Double  // 显式指定
    // ...
}

9.5 扩展系统类型

swift 复制代码
// 给 Int 添加方法
extension Int {
    var squared: Int {
        return self * self
    }
    
    func times(_ action: () -> Void) {
        for _ in 0..<self {
            action()
        }
    }
}

print(5.squared)  // 25
3.times {
    print("Hello!")
}
// Hello!
// Hello!
// Hello!

// 给 Collection 添加方法
extension Collection {
    var isNotEmpty: Bool {
        return !isEmpty
    }
}

[1, 2, 3].isNotEmpty  // true

第10章:泛型编程

10.1 为什么要用泛型?

想象你要写交换两个值的函数:

swift 复制代码
// 只能交换整数
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

// 只能交换字符串
func swapTwoStrings(_ a: inout String, _ b: inout String) {
    let temporaryA = a
    a = b
    b = temporaryA
}

重复代码!用泛型可以写一个通用的:

ini 复制代码
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}

var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)

var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)

<T>类型参数,代表任意类型。

10.2 泛型类型

swift 复制代码
// 泛型栈
struct Stack<Element> {
    private var items: [Element] = []
    
    mutating func push(_ item: Element) {
        items.append(item)
    }
    
    mutating func pop() -> Element? {
        return items.popLast()
    }
    
    var topItem: Element? {
        return items.last
    }
    
    var isEmpty: Bool {
        return items.isEmpty
    }
}

var intStack = Stack<Int>()
intStack.push(1)
intStack.push(2)
print(intStack.pop()!)  // 2

var stringStack = Stack<String>()
stringStack.push("hello")
stringStack.push("world")

10.3 类型约束

swift 复制代码
// 要求 T 必须遵循 Comparable 协议
func findIndex<T: Equatable>(of valueToFind: T, in array: [T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])

常用类型约束:

  • T: Equatable - 可以比较相等
  • T: Comparable - 可以比较大小
  • T: Hashable - 可以作为字典的 key
  • T: SomeProtocol - 遵循某个协议

10.4 关联类型与 where 子句

swift 复制代码
protocol Container {
    associatedtype Item
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}

// 要求两个容器的 Item 相同且可比较
func allItemsMatch<C1: Container, C2: Container>(_ container1: C1, _ container2: C2) -> Bool
    where C1.Item == C2.Item, C1.Item: Equatable {
    
    if container1.count != container2.count {
        return false
    }
    
    for i in 0..<container1.count {
        if container1[i] != container2[i] {
            return false
        }
    }
    return true
}

10.5 不透明返回类型 (some)

swift 复制代码
protocol Shape {
    func draw() -> String
}

struct Triangle: Shape {
    var size: Int
    func draw() -> String {
        var result = [String]()
        for length in 1...size {
            result.append(String(repeating: "*", count: length))
        }
        return result.joined(separator: "\n")
    }
}

struct Square: Shape {
    var size: Int
    func draw() -> String {
        let line = String(repeating: "*", count: size)
        return Array(repeating: line, count: size).joined(separator: "\n")
    }
}

// 返回遵循 Shape 协议的某种类型,但隐藏具体是什么
func makeShape() -> some Shape {
    return Triangle(size: 3)
}

// 也可以返回不同的,只要都是 Shape
func makeRandomShape() -> some Shape {
    Bool.random() ? Triangle(size: 3) : Square(size: 3)
}

some 的好处:

  • 调用者不需要知道具体类型
  • 编译器可以进行类型优化
  • 在 SwiftUI 中非常常用(some View

第11章:错误处理

11.1 定义错误类型

java 复制代码
enum VendingMachineError: Error {
    case invalidSelection                    // 选择无效
    case insufficientFunds(coinsNeeded: Int) // 金额不足,附带需要多少钱
    case outOfStock                          // 缺货
}

任何遵循 Error 协议的类型都可以表示错误。

11.2 抛出错误

swift 复制代码
class VendingMachine {
    var inventory = [
        "Candy Bar": Item(price: 12, count: 7),
        "Chips": Item(price: 10, count: 4),
        "Pretzels": Item(price: 7, count: 11)
    ]
    var coinsDeposited = 0
    
    func vend(itemNamed name: String) throws {
        guard let item = inventory[name] else {
            throw VendingMachineError.invalidSelection
        }
        
        guard item.count > 0 else {
            throw VendingMachineError.outOfStock
        }
        
        guard item.price <= coinsDeposited else {
            throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited)
        }
        
        // 执行购买逻辑
        coinsDeposited -= item.price
        var newItem = item
        newItem.count -= 1
        inventory[name] = newItem
        
        print("Dispensing (name)")
    }
}

throws 标记表示这个函数可能抛出错误。

11.3 处理错误

do-catch

swift 复制代码
let vendingMachine = VendingMachine()
vendingMachine.depositCoins(8)

do {
    try vendingMachine.vend(itemNamed: "Candy Bar")
} catch VendingMachineError.invalidSelection {
    print("Invalid selection.")
} catch VendingMachineError.outOfStock {
    print("Out of stock.")
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
    print("Insufficient funds. Please insert an additional (coinsNeeded) coins.")
} catch {
    print("Unexpected error: (error)")
}

try? - 转换为可选值

csharp 复制代码
// 成功返回结果,失败返回 nil
if let result = try? someThrowingFunction() {
    // 使用结果
} else {
    // 处理失败
}

// 等效于
do {
    let result = try someThrowingFunction()
} catch {
    // 忽略错误
}

try! - 强制解包(危险!)

csharp 复制代码
// 确定不会失败时使用
let result = try! someThrowingFunction()
// 如果失败了,程序会崩溃

11.4 defer - 清理资源

swift 复制代码
func processFile(filename: String) throws -> String {
    let file = try openFile(filename)
    
    defer {
        closeFile(file)  // 无论如何都会执行
    }
    
    if filename.isEmpty {
        throw FileError.invalidName  // 先执行 defer,再抛出错误
    }
    
    return try readFile(file)
}  // 正常返回也会执行 defer

11.5 Result 类型

swift 复制代码
enum NetworkError: Error {
    case badURL
    case noData
    case decodingError
}

func fetchUser(completion: (Result<User, NetworkError>) -> Void) {
    // 模拟网络请求
    let success = Bool.random()
    if success {
        completion(.success(User(name: "Alice")))
    } else {
        completion(.failure(.noData))
    }
}

// 使用
fetchUser { result in
    switch result {
    case .success(let user):
        print("Got user: (user.name)")
    case .failure(let error):
        print("Error: (error)")
    }
}

第12章:内存管理

12.1 ARC (自动引用计数)

Swift 使用 ARC 自动管理内存:

  • 每次创建实例,引用计数 +1
  • 每次引用消失,引用计数 -1
  • 引用计数为 0,内存被释放

你不需要手动管理,但要理解引用关系。

12.2 强引用循环问题

swift 复制代码
class Person {
    let name: String
    var apartment: Apartment?  // 强引用
    
    init(name: String) {
        self.name = name
    }
    
    deinit {
        print("(name) is being deinitialized")
    }
}

class Apartment {
    let unit: String
    var tenant: Person?  // 强引用
    
    init(unit: String) {
        self.unit = unit
    }
    
    deinit {
        print("Apartment (unit) is being deinitialized")
    }
}

var john: Person? = Person(name: "John Appleseed")
var unit4A: Apartment? = Apartment(unit: "4A")

john!.apartment = unit4A
unit4A!.tenant = john

john = nil
unit4A = nil
// 没有打印 deinit 消息!内存泄漏了!

问题: Person 持有 Apartment,Apartment 持有 Person,形成一个环,引用计数永远不会归零。

12.3 弱引用 (Weak Reference)

swift 复制代码
class Apartment {
    let unit: String
    weak var tenant: Person?  // 弱引用!
    
    init(unit: String) {
        self.unit = unit
    }
    
    deinit {
        print("Apartment (unit) is being deinitialized")
    }
}

// 现在当 john = nil,Person 实例会被释放
// 然后 Apartment 的 tenant 自动变成 nil

弱引用的特点:

  • 不增加引用计数
  • 指向的实例释放后自动变成 nil
  • 必须是可选类型(因为可能为 nil)

什么时候用弱引用?

  • 父子关系中的子(如 Apartment 和 tenant)
  • 委托模式 (Delegate pattern)

12.4 无主引用 (Unowned Reference)

swift 复制代码
class Customer {
    let name: String
    var card: CreditCard?
    
    init(name: String) {
        self.name = name
    }
    
    deinit {
        print("(name) is being deinitialized")
    }
}

class CreditCard {
    let number: UInt64
    unowned let customer: Customer  // 无主引用
    
    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    
    deinit {
        print("Card #(number) is being deinitialized")
    }
}

var alice: Customer? = Customer(name: "Alice")
alice!.card = CreditCard(number: 1234_5678_9012_3456, customer: alice!)

alice = nil
// 两个实例都被释放

无主引用的特点:

  • 不增加引用计数
  • 不是可选类型
  • 指向的实例释放后变成 dangling pointer(悬挂指针)

什么时候用无主引用?

  • 确定引用的实例永远比自己活得长
  • 不会造成循环引用的强关系

12.5 闭包中的循环引用

swift 复制代码
class HTMLElement {
    let name: String
    let text: String?
    
    // 闭包捕获 self,形成循环引用!
    lazy var asHTML: () -> String = {
        if let text = self.text {
            return "<(self.name)>(text)</(self.name)>"
        } else {
            return "<(self.name) />"
        }
    }
    
    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
    
    deinit {
        print("(name) is being deinitialized")
    }
}

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "Hello")
let html = paragraph!.asHTML()
paragraph = nil
// 没有 deinit!循环引用!

解决:捕获列表

swift 复制代码
lazy var asHTML: () -> String = { [weak self] in
    guard let self = self else {
        return ""
    }
    if let text = self.text {
        return "<(self.name)>(text)</(self.name)>"
    } else {
        return "<(self.name) />"
    }
}

捕获列表语法:

  • [weak self] - 弱引用 self
  • [unowned self] - 无主引用 self
  • [x = someValue] - 捕获时复制值而不是引用

相关推荐
songgeb6 小时前
Buildable Folder & Group & Folder Reference in Xcode
xcode·swift
songgeb1 天前
UITableView 在 width=0 时 reloadData 被"空转消费"导致 Cell 显示错乱
swift·cursor
小小码农Come on1 天前
VTK-8.2.0+QT5.14.2展示3D图像
qt·3d·swift
wjm0410061 天前
ios学习路线 -- Swift基础(1)
开发语言·ios·swift
2501_915909062 天前
iOS 开发编译与真机调试流程的新思路,用快蝎 IDE 构建应用
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
2501_915918412 天前
苹果应用开发编译流程,用快蝎(kxapp)工具完成 iOS 构建与调试
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
东坡肘子3 天前
Macbook Neo:苹果重回校园的起点 -- 肘子的 Swift 周报 #126
人工智能·swiftui·swift
TT_Close8 天前
【Flutter×鸿蒙】FVM 不认鸿蒙 SDK?4步手动塞进去
flutter·swift·harmonyos
张江8 天前
Swift Concurrency学习
swift