Protocol、associatedtype 和 typealias 配合的真好

在 Swift 中,associatedtype 是协议(Protocol)中使用的关键字,用于声明一个关联类型,它允许协议在定义时不指定具体类型,而是在遵循该协议的类型中确定具体类型。这使得协议更加灵活和通用。以下是 associatedtype 的不同使用场景及示例:


1. 容器协议(Container Protocol)

场景:定义一个通用的容器协议,可以存储任意类型的元素。
示例

protocol Container {
    associatedtype Item
    var count: Int { get }
    mutating func append(_ item: Item)
    subscript(i: Int) -> Item { get }
}

struct Stack<Element>: Container {
    typealias Item = Element // 明确关联类型为 Element
    private var items = [Element]()
    
    var count: Int { items.count }
    
    mutating func append(_ item: Element) {
        items.append(item)
    }
    
    subscript(i: Int) -> Element {
        return items[i]
    }
}

var stack = Stack<Int>()
stack.append(1)
print(stack[0]) // 输出: 1

说明

  • Container 协议中的 Item 是关联类型,具体类型由遵循协议的类型(如 Stack)决定。
  • Stack<Element> 通过 typealias Item = Element 明确 Item 的具体类型。

2. 协议中的泛型约束

场景:协议的方法需要操作特定类型的关联类型,比如 Equatable 约束。
示例

protocol EquatableContainer {
    associatedtype Item: Equatable // 关联类型必须遵循 Equatable
    func contains(_ item: Item) -> Bool
}

struct ArrayContainer<T: Equatable>: EquatableContainer {
    typealias Item = T
    private var items = [T]()
    
    func contains(_ item: T) -> Bool {
        return items.contains(item)
    }
}

let container = ArrayContainer<Int>()
print(container.contains(5)) // 输出: false(假设数组为空)

说明

  • Item 被约束为 Equatable,因此 ArrayContainer 只能用于遵循 Equatable 的类型(如 IntString)。

3. 多个关联类型

场景:协议需要多个泛型类型,比如 Key-Value 存储。
示例

protocol KeyValueStore {
    associatedtype Key: Hashable
    associatedtype Value
    func set(_ value: Value, forKey key: Key)
    func get(forKey key: Key) -> Value?
}

struct DictionaryStore<K: Hashable, V>: KeyValueStore {
    typealias Key = K
    typealias Value = V
    private var dict = [K: V]()
    
    func set(_ value: V, forKey key: K) {
        dict[key] = value
    }
    
    func get(forKey key: K) -> V? {
        return dict[key]
    }
}

let store = DictionaryStore<String, Int>()
store.set(42, forKey: "age")
print(store.get(forKey: "age")) // 输出: Optional(42)

说明

  • 协议定义了两个关联类型 KeyValue,分别由遵循协议的类型具体化。

4. 关联类型 + 协议组合

场景:关联类型需要遵循另一个协议。
示例

protocol Renderable {
    func render()
}

protocol Renderer {
    associatedtype Model: Renderable // 关联类型必须遵循 Renderable
    func render(_ model: Model)
}

struct ShapeRenderer: Renderer {
    typealias Model = Circle // Circle 遵循 Renderable
    
    func render(_ model: Circle) {
        model.render()
    }
}

struct Circle: Renderable {
    func render() {
        print("绘制圆形")
    }
}

let renderer = ShapeRenderer()
renderer.render(Circle()) // 输出: "绘制圆形"

说明

  • Renderer 的关联类型 Model 必须遵循 Renderable,因此 ShapeRenderer 只能用于 Renderable 类型(如 Circle)。

5. 关联类型 + 类型擦除(Type Erasure)

场景:隐藏关联类型的具体类型(常见于 SwiftUI 的 SomeView)。
示例

protocol DataFetcher {
    associatedtype Data
    func fetch() -> Data
}

struct AnyDataFetcher<D>: DataFetcher {
    typealias Data = D
    private let _fetch: () -> D
    
    init<T: DataFetcher>(_ fetcher: T) where T.Data == D {
        self._fetch = fetcher.fetch
    }
    
    func fetch() -> D {
        return _fetch()
    }
}

struct StringFetcher: DataFetcher {
    typealias Data = String
    func fetch() -> String { return "Hello" }
}

let fetcher = AnyDataFetcher(StringFetcher())
print(fetcher.fetch()) // 输出: "Hello"

说明

  • AnyDataFetcher 是一个类型擦除包装器,隐藏了底层 DataFetcher 的具体类型。

6. 关联类型 + 泛型函数

场景:协议方法需要返回关联类型的值。
示例

protocol Factory {
    associatedtype Product
    func make() -> Product
}

struct CarFactory: Factory {
    typealias Product = String
    func make() -> String { return "Tesla" }
}

struct PhoneFactory: Factory {
    typealias Product = Int
    func make() -> Int { return 42 } // 可以是任意类型
}

let carFactory = CarFactory()
print(carFactory.make()) // 输出: "Tesla"

说明

  • Product 的具体类型由遵循协议的类型决定(可以是 StringInt 等)。

7. 关联类型 + Self 约束

场景:协议方法需要返回或操作遵循协议的类型自身。
示例

protocol Clonable {
    associatedtype CloneType: Clonable
    func clone() -> CloneType
}

class Document: Clonable {
    typealias CloneType = Document
    
    func clone() -> Document {
        return Document()
    }
}

let doc = Document()
let clonedDoc = doc.clone()

说明

  • CloneType 被约束为 Clonable,因此 clone() 必须返回遵循 Clonable 的类型。

总结

场景 关键点 示例
容器协议 存储任意类型元素 Stack<Element>
泛型约束 关联类型需遵循协议 Item: Equatable
多关联类型 多个泛型类型 KeyValueStore
协议组合 关联类型遵循其他协议 Model: Renderable
类型擦除 隐藏具体类型 AnyDataFetcher
泛型函数 方法返回关联类型 Factory
Self 约束 操作协议自身类型 Clonable

核心作用
associatedtype 让协议可以定义“抽象类型”,由遵循协议的类型决定具体类型,从而实现真正的泛型协议(Protocol with Generic)。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 泛型(Generics) 泛型代码允许你定义适用于任何类型的,符合你设置的要求的,灵活且可重用的 函数和类型。泛型...
    果啤阅读 704评论 0 0
  • 1. 前言 泛型代码让你能根据你所定义的要求写出可以用于任何类型的灵活的、可复用的函数。你可以编写出可复用、意图表...
    搬运工iOS橙阅读 699评论 0 1
  • 泛型代码让你能根据自定义的需求,编写出适用于任意类型的、灵活可复用的函数及类型。 你可避免编写重复的代码,而是用一...
    DevXue阅读 211评论 0 0
  • Swift — 泛型(Generics) [TOC] 本文将介绍泛型的一些用法、关联类型、where语句,以及对泛...
    just东东阅读 1,354评论 0 3
  • 前言 SwiftGG 翻译组的 《The Swift Programming Language》in Chines...
    四月_Hsu阅读 607评论 0 2