Swift-泛型

350 阅读3分钟

简介

泛型是 Swift 最强大的特性之一,很多 Swift 标准库是基于泛型代码构建的。

泛型是使代码具备通用性和复用性的重要特性。泛型允许我们编写可以适用于多种类型的灵活函数和类型,而不必重复编写多个函数版本。

在没有泛型的情况下,我们常常需要为不同类型写重复的代码。

func swapInts(_ a: inout Int, _ b: inout Int) { 
    ... 
}

func swapStrings(_ a: inout String, _ b: inout String) { 
    ... 
}

泛型的核心是占位类型,即在定义时使用占位符表示数据类型,而具体类型由调用时决定。Swift 一般用尖括号 < > 包裹。

1.泛型函数

func swapValues<T>(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}
func makeArray<T>(repeating item: T, count: Int) -> [T] {
    return Array(repeating: item, count: count)
}

这里 T 是泛型参数,占位符可以代表任意类型。在函数调用时,编译器会根据传入的参数类型推断出 T 的实际类型。

除了T,也可以用其他任意代号,如ElementBase

调用

self.swapValues(23, 44)
self.swapValues("Ksdme", "Plkdfm")

2.泛型类型

  • 泛型结构体
struct Stack<Element> {
    private var items: [Element] = []

    mutating func push(_ item: Element) {
        items.append(item)
    }

    mutating func pop() -> Element {
        return items.removeLast()
    }
}

调用

var intStack = Stack<Int>()
intStack.push(10)
  • 泛型类型
class Box<T> {
    var value: T
    init(_ value: T) {
        self.value = value
    }
}
  • 泛型枚举
enum Result<Success, Failure> {
    case success(Success)
    case failure(Failure)
}

3.泛型协议

关联类型(Associated Types)

用在协议中定义一个“占位类型”。具体类型,由协议遵守者决定。

protocol Container {
    associatedtype XO

    mutating func append(_ item: XO)
    
    var count: Int { get }
    
    subscript(i: Int) -> XO { get }
}

遵守协议,具体实现,编译器会推断具体类型

struct IntStack: Container {

    var items: [Int] = []

    mutating func append(_ item: Int) {
        items.append(item)
    }

    var count: Int { return items.count }

    subscript(i: Int) -> Int {
        return items[i]
    }
}

另外,也可使用关键字associatedtype+typealias,决定具体类型

protocol Stackable {

    associatedtype Element    

    mutating func push(_ element: Element)  
    mutating func pop() -> Element
}

class StringStack : Stackable {

    typealias Element = String   // 确定 关联类型为 String
    
    var elements = [String]()
    
    func push(_ element: String) { 
        elements.append(element) 
    } 
    func pop() -> String { 
        elements.removeLast() 
    }
}

泛型约束

默认泛型参数可以是任何类型,但你可以限制它必须符合某个协议。

  • 使用: XXX,规定该类型需遵从XXX协议

func findIndex<T: Equatable>(of value: T, in array: [T]) -> Int? {
    for (index, element) in array.enumerated() {
        if element == value {
            return index
        }
    }
    return nil
}
  • 使用 where 语句:添加更复杂的约束条件

在函数中

func allItemsMatch<C1: Container, C2: Container>(_ a: C1, _ b: C2) -> Bool
where C1.Item == C2.Item, C1.Item: Equatable {
    ...
}

func fetch<T: Decodable>(url: URL, completion: @escaping (Result<T, Error>) -> Void) {
    ...
}

在协议

public struct KingfisherWrapper<Base>{
    public let base: Base
    public init(_ base: Base) {
        self.base = base
    }
}


extension KingfisherWrapper where Base: UIButton {

}

示例

我们写了一个工具方法,用于加载JSON文件并转化为 User实例

// 传入JSON数据文件名,最后转化为 实例
func decode(_ file: String) -> [String: User] {

    // 查找文件路径
    guard let url = Bundle.url(forResource: file, withExtension: nil) else {
        fatalError()
    }
    // 根据路径把文件转为Data
    guard let data = try? Data(contentsOf: url) else {
        fatalError()
    }

    // 把JSON数据转化为 实例
    let decoder = JSONDecoder()
    guard let loaded = try? decoder.decode([String: User].self, from: data) else {
        fatalError()
    }
    return loaded
}

如果,我们需要处理除User之外的其他类型,这时就是泛型的好处了。

// 传入JSON数据文件名,最后转化为 实例
func decode<T: Codable>(_ file: String) -> T {

    // 查找文件路径
    guard let url = Bundle.url(forResource: file, withExtension: nil) else {
        fatalError()
    }
    // 根据路径把文件转为Data
    guard let data = try? Data(contentsOf: url) else {
        fatalError()
    }

    // 把JSON数据转化为 实例
    let decoder = JSONDecoder()
    guard let loaded = try? decoder.decode(T.self, from: data) else {
        fatalError()
    }
    return loaded
}

注意,此例中T: Codable表示该泛型必须是 遵守 Codable协议的

此外,调用时需要 带上 类型注释,因为此时无法进行 类型推断

let user: [String: User] = Bundle.main.decode("user.json")