欢迎来到本系列教程的第六篇。在之前的文章中,你已经学会了Swift的基础语法、函数与闭包,以及如何使用结构体和类来定义自己的类型。现在,你的代码已经有了"骨架"和"血肉",但如何让不同类型的对象遵循统一的行为规范?如何在不修改原有类型定义的前提下,为它添加新的功能?
这就是协议(Protocol) 和扩展(Extension) 要解决的问题。协议定义了一组"规则"或"契约",任何遵循协议的类型都必须实现这些规则。扩展则允许你为现有的类型(甚至是系统内置类型,如Int、String)添加新的方法或计算属性。
在这一篇中,你将学到:
-
协议(Protocol)
-
协议的基本定义与遵循
-
协议中的属性要求、方法要求、初始化器要求
-
协议作为类型使用
-
协议继承与组合
-
可选协议要求(
@objcoptional)
-
-
扩展(Extension)
-
用扩展添加计算属性和方法
-
用扩展遵循协议
-
协议扩展提供默认实现
-
-
面向协议编程(POP)
-
协议与面向对象编程的比较
-
Swift标准库中的常见协议(
Equatable、Comparable、Hashable、CustomStringConvertible等)
-
-
SwiftUI中的协议实践
-
使用协议抽象网络请求
-
为视图组件定义通用协议
-
每一部分都会配有Playground示例和SwiftUI实战代码。这一篇学完之后,你将掌握Swift最核心的设计哲学之一------面向协议编程,写出更加灵活、可测试、可复用的代码。
一、为什么需要协议?
假设你在开发一个游戏,里面有多种角色:战士、法师、弓箭手。每个角色都有攻击能力,但攻击方式完全不同。没有协议的情况下,你可能会这样写:
swift
struct Warrior {
func attack() {
print("战士挥舞大剑,造成50点伤害")
}
}
struct Mage {
func castSpell() {
print("法师释放火球,造成80点伤害")
}
}
struct Archer {
func shoot() {
print("弓箭手射出箭矢,造成40点伤害")
}
}
// 如果要统一处理所有角色的攻击,需要分别判断类型
func performAttack(role: Any) {
if let warrior = role as? Warrior {
warrior.attack()
} else if let mage = role as? Mage {
mage.castSpell()
} else if let archer = role as? Archer {
archer.shoot()
}
}
这种方法有很多问题:
-
每增加一个新角色,
performAttack函数就要修改 -
不同角色的攻击方法名称不一致(
attack、castSpell、shoot) -
代码冗长,难以维护
协议就是解决这个问题的完美方案:它定义了一个统一的接口,所有角色都可以遵循这个协议,并实现自己的版本:
swift
protocol Attackable {
func attack()
}
struct Warrior: Attackable {
func attack() {
print("战士挥舞大剑,造成50点伤害")
}
}
struct Mage: Attackable {
func attack() {
print("法师释放火球,造成80点伤害")
}
}
struct Archer: Attackable {
func attack() {
print("弓箭手射出箭矢,造成40点伤害")
}
}
// 统一处理
func performAttack(roles: [Attackable]) {
for role in roles {
role.attack()
}
}
现在,performAttack函数只需要接受Attackable协议类型的数组,任何遵循该协议的类型都可以被传入,而且调用的是各自实现的attack方法。
二、协议的基本使用
2.1 定义协议
协议使用protocol关键字定义,可以包含属性要求、方法要求和初始化器要求:
swift
protocol Identifiable {
var id: String { get } // 只读属性要求
var name: String { get set } // 可读可写属性要求
func identify() -> String // 方法要求
init(id: String, name: String) // 初始化器要求
}
-
属性要求中的
{ get }表示遵循该协议的类型必须至少提供一个只读属性(可以是计算属性,也可以是存储属性) -
{ get set }表示必须提供一个可读可写的属性 -
方法要求不需要写函数体,只写签名
-
初始化器要求需要遵循的类型实现对应的
init
2.2 遵循协议
类型遵循协议时,必须实现所有要求:
swift
struct User: Identifiable {
var id: String // 满足 id 的 get 要求(存储属性自然可读)
var name: String // 满足 name 的 get set 要求
func identify() -> String {
return "用户:\(name) (ID: \(id))"
}
init(id: String, name: String) {
self.id = id
self.name = name
}
}
class Product: Identifiable {
var id: String
var name: String
func identify() -> String {
return "商品:\(name) (编号: \(id))"
}
required init(id: String, name: String) { // 类需要 required 关键字
self.id = id
self.name = name
}
}
注意:对于类,初始化器要求需要用required关键字标记,以确保所有子类也遵循该协议。
2.3 协议作为类型
协议本身不实例化,但可以作为类型使用:作为参数类型、返回类型、变量/常量类型、数组/字典的元素类型等。
swift
let identifiableThings: [Identifiable] = [
User(id: "u1", name: "张三"),
Product(id: "p1", name: "iPhone")
]
for thing in identifiableThings {
print(thing.identify())
}
2.4 协议继承
协议可以继承一个或多个其他协议,组合更具体的要求:
swift
protocol Animal {
var name: String { get }
func makeSound()
}
protocol Pet {
var owner: String { get set }
func play()
}
// Dog 协议同时继承了 Animal 和 Pet
protocol Dog: Animal, Pet {
var breed: String { get }
}
struct GoldenRetriever: Dog {
let name: String
var owner: String
let breed: String = "金毛"
func makeSound() {
print("汪汪!")
}
func play() {
print("\(name) 正在玩飞盘")
}
}
2.5 协议组合(&)
有时你需要一个类型同时遵循多个协议,可以使用协议组合语法&:
swift
protocol Flyable {
func fly()
}
protocol Swimmable {
func swim()
}
// 需要同时具备飞行和游泳能力
func performDuel(creature: Flyable & Swimmable) {
creature.fly()
creature.swim()
}
// 也可以作为变量类型
var amazingCreature: Flyable & Swimmable
协议组合可以包含最多一个类类型(放在最前面):
swift
class LivingThing { }
func process(thing: LivingThing & Flyable & Swimmable) { }
2.6 可选协议要求(@objc optional)
Swift的协议默认要求所有成员都必须被实现。如果你想要"可选"要求(即遵循的类型可以选择性地实现),需要使用@objc标记协议,并用optional关键字标记可选方法。注意:这会让协议只能被类遵循,不能用于结构体或枚举。
swift
@objc protocol Configurable {
@objc optional func configure() // 可选方法
func mandatoryMethod() // 必须实现
}
class MyView: Configurable {
func mandatoryMethod() {
print("实现了必须的方法")
}
// configure() 可以不实现
}
在Swift开发中,除非与Objective-C互操作,否则更推荐使用协议扩展 来提供默认实现,而不是使用@objc optional。
三、扩展(Extension)
扩展让你在不修改原始代码的情况下,为现有的类、结构体、枚举或协议添加新功能。
3.1 添加计算属性和方法
swift
extension Int {
// 添加计算属性
var squared: Int {
return self * self
}
var isEven: Bool {
return self % 2 == 0
}
// 添加方法
func times(_ closure: () -> Void) {
for _ in 1...self {
closure()
}
}
}
print(5.squared) // 25
print(8.isEven) // true
3.times {
print("Hello")
}
// 输出三次 Hello
3.2 扩展遵循协议
这是扩展最常见的用途之一:让一个现有类型遵循某个协议,而不需要修改其原始定义。
swift
struct Book {
let title: String
let author: String
let year: Int
}
// 让 Book 遵循 CustomStringConvertible 协议
extension Book: CustomStringConvertible {
var description: String {
return "《\(title)》,作者:\(author),\(year)年"
}
}
let book = Book(title: "三体", author: "刘慈欣", year: 2008)
print(book) // 《三体》,作者:刘慈欣,2008年
3.3 扩展添加初始化器
你可以通过扩展添加新的初始化器,但不会移除系统自动提供的成员初始化器(这与在原始定义中添加自定义初始化器不同)。
swift
struct Size {
var width: Double
var height: Double
}
extension Size {
init(side: Double) {
self.width = side
self.height = side
}
}
let size1 = Size(width: 10, height: 20) // 成员初始化器仍然可用
let size2 = Size(side: 15) // 扩展添加的初始化器
3.4 扩展嵌套类型
扩展还可以添加嵌套类型:
swift
extension String {
enum ValidationType {
case email, phone, url
}
func isValid(for type: ValidationType) -> Bool {
switch type {
case .email:
return contains("@") && contains(".")
case .phone:
return count == 11 && allSatisfy { $0.isNumber }
case .url:
return hasPrefix("http://") || hasPrefix("https://")
}
}
}
let email = "test@example.com"
print(email.isValid(for: .email)) // true
四、协议扩展 ------ Swift的强大武器
协议扩展是Swift最强大的特性之一。它允许你为协议提供默认实现,这样遵循协议的类型可以"免费"获得这些实现,除非它们需要自定义。
4.1 提供默认实现
swift
protocol Greetable {
var name: String { get }
func greet()
}
// 通过协议扩展提供默认实现
extension Greetable {
func greet() {
print("你好,我是\(name)")
}
}
struct Person: Greetable {
var name: String
// 不需要实现 greet(),因为已经有了默认实现
}
struct Robot: Greetable {
var name: String
// 可以自定义实现,覆盖默认版本
func greet() {
print("哔哔——我是机器人\(name),很高兴认识你")
}
}
let person = Person(name: "张三")
person.greet() // "你好,我是张三"
let robot = Robot(name: "R2-D2")
robot.greet() // "哔哔——我是机器人R2-D2,很高兴认识你"
4.2 为协议扩展添加条件约束
你可以使用where子句限制扩展只在某些条件下生效:
swift
protocol Container {
associatedtype Item // 关联类型,稍后会讲
var items: [Item] { get set }
func add(_ item: Item)
}
extension Container where Item == String {
// 只有当 Item 是 String 类型时,这个扩展才生效
func printAllUppercased() {
items.forEach { print($0.uppercased()) }
}
}
struct StringContainer: Container {
typealias Item = String
var items: [String] = []
mutating func add(_ item: String) {
items.append(item)
}
}
var container = StringContainer()
container.add("hello")
container.add("world")
container.printAllUppercased() // HELLO WORLD
4.3 协议扩展与静态派发
协议扩展中的方法有两种派发方式:
-
在协议本身中声明的方法:动态派发(根据实际类型调用)
-
只在扩展中定义的方法:静态派发(根据协议类型调用)
swift
protocol Animal {
func sound() // 协议中声明
}
extension Animal {
func sound() { // 默认实现
print("动物发出声音")
}
func move() { // 只在扩展中定义
print("动物在移动")
}
}
struct Dog: Animal {
func sound() {
print("汪汪")
}
func move() {
print("狗在奔跑")
}
}
let animal: Animal = Dog()
animal.sound() // "汪汪"(动态派发,调用 Dog 的版本)
animal.move() // "动物在移动"(静态派发,调用协议扩展的版本)
理解这个区别对于写出预期行为的代码非常重要。
五、Swift标准库中的常见协议
Swift标准库大量使用了协议。熟悉这些协议会让你的代码更好地融入Swift生态。
5.1 Equatable ------ 相等性比较
Equatable协议要求类型实现==运算符。Swift会为只包含Equatable成员的结构体自动合成实现。
swift
struct Point: Equatable {
var x: Int
var y: Int
// 不需要手动实现 ==,Swift 会自动合成
}
let p1 = Point(x: 0, y: 0)
let p2 = Point(x: 0, y: 0)
print(p1 == p2) // true
// 如果自定义比较逻辑,可以手动实现
struct Person: Equatable {
var id: String
var name: String
static func == (lhs: Person, rhs: Person) -> Bool {
return lhs.id == rhs.id // 只根据 id 判断相等
}
}
5.2 Comparable ------ 比较大小
Comparable继承自Equatable,要求实现<运算符。
swift
struct Student: Comparable {
var name: String
var score: Int
static func < (lhs: Student, rhs: Student) -> Bool {
return lhs.score < rhs.score
}
}
let students = [
Student(name: "张三", score: 85),
Student(name: "李四", score: 92),
Student(name: "王五", score: 78)
]
let sorted = students.sorted() // 按照分数升序
print(sorted.map { "\($0.name):\($0.score)" })
// ["王五:78", "张三:85", "李四:92"]
5.3 Hashable ------ 哈希值
Hashable协议要求类型提供一个哈希值,用于作为字典的键或集合的元素。Swift会自动为所有属性都是Hashable的结构体合成实现。
swift
struct User: Hashable {
let id: Int
let username: String
}
let user1 = User(id: 1, username: "alice")
let user2 = User(id: 2, username: "bob")
let userSet: Set<User> = [user1, user2] // 可以作为 Set 的元素
let userDict: [User: String] = [user1: "Alice info"] // 可以作为字典的键
5.4 CustomStringConvertible ------ 自定义字符串表示
实现这个协议可以让你控制print()输出内容的格式。
swift
struct Color: CustomStringConvertible {
var red: Int
var green: Int
var blue: Int
var description: String {
return "RGB(\(red), \(green), \(blue))"
}
}
let c = Color(red: 255, green: 128, blue: 0)
print(c) // RGB(255, 128, 0)
5.5 CaseIterable ------ 枚举的遍历
让枚举遵循CaseIterable可以自动获得一个allCases属性,包含所有枚举值。
swift
enum Season: CaseIterable {
case spring, summer, autumn, winter
}
for season in Season.allCases {
print(season)
}
// spring, summer, autumn, winter
5.6 Identifiable ------ 唯一标识
SwiftUI中大量使用Identifiable协议,要求类型提供一个id属性。ForEach需要元素遵循Identifiable或者手动提供id参数。
swift
struct Task: Identifiable {
let id = UUID() // 或者使用 Int、String 等
var title: String
var isCompleted: Bool
}
六、面向协议编程(POP)
面向协议编程是一种设计范式,强调使用协议和协议扩展来定义和组合行为,而不是传统的继承体系。
6.1 POP vs OOP
| 特性 | 面向对象编程(OOP) | 面向协议编程(POP) |
|---|---|---|
| 代码复用方式 | 类继承 | 协议扩展 + 组合 |
| 类型关系 | 单一继承链(一个子类只有一个父类) | 多协议遵循(一个类型可以遵循多个协议) |
| 值类型支持 | 不支持(类是引用类型) | 完美支持结构体和枚举 |
| 基类强制 | 需要有一个共同的基类 | 没有基类要求 |
| 耦合度 | 较高(子类依赖父类实现) | 较低(协议只定义接口) |
6.2 POP 实践示例
假设我们要创建一个绘图系统,支持绘制各种形状,并且某些形状可以填充颜色,某些可以描边。
OOP方式的问题:如果使用继承,可能会产生"菱形继承"问题,或者需要多重继承(Swift不支持)。
POP方式:
swift
import Foundation
import CoreGraphics
// 基础协议
protocol Drawable {
func draw(in context: CGContext)
}
// 可填充协议
protocol Fillable {
var fillColor: CGColor { get set }
func fill()
}
// 可描边协议
protocol Stroked {
var strokeColor: CGColor { get set }
var lineWidth: CGFloat { get set }
func stroke()
}
// 圆形:可以绘制,也可以填充、描边
struct Circle: Drawable, Fillable, Stroked {
var center: CGPoint
var radius: CGFloat
var fillColor: CGColor
var strokeColor: CGColor
var lineWidth: CGFloat
func draw(in context: CGContext) {
// 绘制圆形的具体逻辑
}
func fill() {
// 填充逻辑
}
func stroke() {
// 描边逻辑
}
}
// 线段:只能描边,不能填充
struct Line: Drawable, Stroked {
var start: CGPoint
var end: CGPoint
var strokeColor: CGColor
var lineWidth: CGFloat
func draw(in context: CGContext) {
// 绘制线段的逻辑
}
func stroke() {
// 描边逻辑
}
}
// 通过协议扩展提供默认实现
extension Drawable {
func drawWithDebug() {
print("开始绘制")
// 实际绘制代码
print("绘制完成")
}
}
这种设计让你可以灵活地组合行为,而不受继承层次的限制。
七、关联类型(Associated Types)
协议中可以使用associatedtype定义一个占位符类型。遵循协议的类型需要指定这个占位符的具体类型。
swift
protocol Container {
associatedtype Item
var count: Int { get }
mutating func add(_ item: Item)
func get(at index: Int) -> Item?
}
struct IntContainer: Container {
typealias Item = Int // 可以显式指定,也可以让编译器推断
private var items: [Int] = []
var count: Int { items.count }
mutating func add(_ item: Int) {
items.append(item)
}
func get(at index: Int) -> Int? {
guard index >= 0 && index < items.count else { return nil }
return items[index]
}
}
// 泛型容器
struct GenericContainer<T>: Container {
typealias Item = T
private var items: [T] = []
var count: Int { items.count }
mutating func add(_ item: T) {
items.append(item)
}
func get(at index: Int) -> T? {
guard index >= 0 && index < items.count else { return nil }
return items[index]
}
}
带有关联类型的协议不能直接作为类型使用(例如var c: Container是编译错误的),只能作为泛型约束使用:
swift
func printFirstItem<C: Container>(_ container: C) where C.Item: CustomStringConvertible {
if let item = container.get(at: 0) {
print(item)
}
}
八、SwiftUI实战:使用协议构建可插拔的网络层
让我们通过一个实际场景来综合运用协议和扩展:为App构建一个灵活的网络请求层。
swift
import SwiftUI
// MARK: - 网络协议定义
/// 请求协议
protocol Request {
associatedtype Response: Decodable
var url: URL { get }
var method: HTTPMethod { get }
var headers: [String: String] { get }
var parameters: [String: Any]? { get }
}
/// HTTP 方法
enum HTTPMethod: String {
case get = "GET"
case post = "POST"
case put = "PUT"
case delete = "DELETE"
}
/// 网络服务协议
protocol NetworkService {
func send<R: Request>(_ request: R) async throws -> R.Response
}
/// 日志记录协议
protocol Logger {
func log(_ message: String, level: LogLevel)
}
enum LogLevel: String {
case info = "ℹ️"
case warning = "⚠️"
case error = "❌"
}
// MARK: - 默认实现
/// 网络服务的默认实现(使用 URLSession)
struct URLSessionNetworkService: NetworkService {
private let session: URLSession
private let logger: Logger?
init(session: URLSession = .shared, logger: Logger? = nil) {
self.session = session
self.logger = logger
}
func send<R: Request>(_ request: R) async throws -> R.Response {
var urlRequest = URLRequest(url: request.url)
urlRequest.httpMethod = request.method.rawValue
urlRequest.allHTTPHeaderFields = request.headers
// 添加参数(简化版,实际需要正确处理 GET/POST 参数)
if let parameters = request.parameters, request.method == .post {
urlRequest.httpBody = try? JSONSerialization.data(withJSONObject: parameters)
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
logger?.log("发送请求: \(request.url)", level: .info)
let (data, response) = try await session.data(for: urlRequest)
guard let httpResponse = response as? HTTPURLResponse else {
throw NetworkError.invalidResponse
}
guard (200...299).contains(httpResponse.statusCode) else {
logger?.log("请求失败: \(httpResponse.statusCode)", level: .error)
throw NetworkError.httpError(statusCode: httpResponse.statusCode)
}
do {
let decoded = try JSONDecoder().decode(R.Response.self, from: data)
logger?.log("请求成功,数据已解析", level: .info)
return decoded
} catch {
logger?.log("解析失败: \(error)", level: .error)
throw NetworkError.decodingFailed(error)
}
}
}
/// 控制台日志器的默认实现
struct ConsoleLogger: Logger {
func log(_ message: String, level: LogLevel) {
print("\(level.rawValue) \(message)")
}
}
// MARK: - 具体请求
/// 获取用户信息的请求
struct GetUserRequest: Request {
typealias Response = User
let userId: Int
var url: URL {
URL(string: "https://api.example.com/users/\(userId)")!
}
var method: HTTPMethod { .get }
var headers: [String: String] { [:] }
var parameters: [String: Any]? { nil }
}
/// 用户模型
struct User: Codable, Identifiable {
let id: Int
let name: String
let email: String
}
/// 登录请求
struct LoginRequest: Request {
typealias Response = AuthToken
let username: String
let password: String
var url: URL {
URL(string: "https://api.example.com/login")!
}
var method: HTTPMethod { .post }
var headers: [String: String] { [:] }
var parameters: [String: Any]? {
return ["username": username, "password": password]
}
}
/// 认证令牌
struct AuthToken: Codable {
let token: String
let expiresIn: Int
}
// MARK: - 错误类型
enum NetworkError: LocalizedError {
case invalidResponse
case httpError(statusCode: Int)
case decodingFailed(Error)
var errorDescription: String? {
switch self {
case .invalidResponse:
return "无效的服务器响应"
case .httpError(let code):
return "HTTP错误: \(code)"
case .decodingFailed(let error):
return "数据解析失败: \(error.localizedDescription)"
}
}
}
// MARK: - SwiftUI 视图
struct UserProfileView: View {
@State private var user: User?
@State private var isLoading = false
@State private var errorMessage: String?
// 注入网络服务(方便测试和替换)
private let networkService: NetworkService
init(networkService: NetworkService = URLSessionNetworkService(logger: ConsoleLogger())) {
self.networkService = networkService
}
var body: some View {
VStack(spacing: 20) {
if isLoading {
ProgressView("加载中...")
} else if let user = user {
VStack(spacing: 10) {
Text(user.name)
.font(.largeTitle)
Text(user.email)
.font(.title3)
.foregroundColor(.secondary)
}
} else if let errorMessage = errorMessage {
Text(errorMessage)
.foregroundColor(.red)
Button("重试") {
Task { await loadUser() }
}
} else {
Button("加载用户信息") {
Task { await loadUser() }
}
.buttonStyle(.borderedProminent)
}
}
.padding()
}
private func loadUser() async {
isLoading = true
errorMessage = nil
let request = GetUserRequest(userId: 1)
do {
let user = try await networkService.send(request)
self.user = user
} catch {
errorMessage = error.localizedDescription
}
isLoading = false
}
}
#Preview {
// 可以替换为 Mock 网络服务进行预览
UserProfileView()
}
这个例子展示了:
-
使用协议定义抽象的
Request和NetworkService -
协议扩展提供默认日志行为
-
具体的请求类型遵循
Request协议,每个请求自己定义URL、参数等 -
SwiftUI视图通过协议类型依赖网络服务,易于测试和替换
-
关联类型
associatedtype在Request协议中的应用
九、常见错误与调试
9.1 "Protocol 'X' can only be used as a generic constraint because it has Self or associated type requirements"
当协议有关联类型或使用Self时,它不能被直接作为类型使用(称为"不透明协议"或"泛型协议")。
swift
// 错误
var container: Container = IntContainer()
// 正确:使用泛型或类型擦除
func process<C: Container>(_ container: C) { }
9.2 "Type 'X' does not conform to protocol 'Y'"
这意味着你的类型没有实现协议要求的所有成员。检查你是否:
-
实现了所有属性(get/set要求正确)
-
实现了所有方法
-
实现了所有初始化器(类需要
required)
9.3 "Cannot assign value of type 'X' to type 'any Y'"
在Swift 5.7+中,使用any关键字标记存在类型(existential type):
swift
var animal: any Animal = Dog() // 显式使用 any
9.4 "Extension of protocol 'X' cannot provide an initializer"
协议扩展不能添加指定初始化器,只能添加便捷初始化器(对于类)或新的初始化器(对于结构体,但可能会与成员初始化器冲突)。
十、总结与思考题
今天的内容涵盖了Swift协议与扩展的完整知识体系:
-
协议:定义规范,支持属性、方法、初始化器要求,支持继承和组合
-
扩展:为现有类型添加功能,让类型遵循协议
-
协议扩展:提供默认实现,这是面向协议编程的基石
-
Swift标准库协议 :
Equatable、Comparable、Hashable、Identifiable等 -
关联类型:让协议具备泛型能力
-
面向协议编程:比继承更灵活的设计范式
-
实战应用:构建可插拔的网络层
请尝试以下练习:
-
定义一个
Playable协议,包含duration: TimeInterval属性和play()方法。让Song和Podcast结构体遵循该协议,然后编写一个播放列表函数,接受[Playable]数组并依次播放(只打印信息即可)。 -
使用扩展为
String添加一个isValidEmail计算属性和一个masked()方法(将除前三位和后四位以外的字符替换为****)。 -
定义一个
Randomizable协议,提供一个static func random() -> Self方法要求。然后通过协议扩展为Int、Double、Bool提供默认实现(提示:使用Int.random、Double.random、Bool.random)。 -
扩展图书管理App,定义一个
Exportable协议,包含func export() -> String方法。让Book结构体遵循该协议,将图书信息导出为JSON字符串。然后添加一个"导出"按钮,将藏书列表导出并打印。
在下一篇中,我们将学习Swift的错误处理与泛型。错误处理让你优雅地处理程序运行中的异常情况;泛型让你写出适用于多种类型的灵活代码。这两个特性将让你的代码更加健壮和通用。
协议是Swift的灵魂之一。掌握了它,你就能理解为什么Swift既简洁又强大。继续保持这个学习节奏,你已经走了很远!