第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- 可以作为字典的 keyT: 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]- 捕获时复制值而不是引用