Swift-泛型

328 阅读3分钟

简介

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

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

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

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

泛型代码让你能根据你所定义的要求,写出可以用于任何类型的灵活的、可复用的函数。

  • 泛型可以将类型参数化,提高代码复用率,减少代码量
func swapValues<T>(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}

1.泛型函数

<T>表明类型可变。至于是具体什么类型,由调用函数时,实参的类型决定。

func makeArray<T>(repeating item: T, count: Int) -> [T] {

    return Array(repeating: item, count: count)
}

2.泛型约束

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

func findIndex<T: Equatable>(of value: T, in array: [T]) -> Int? {
    for (index, element) in array.enumerated() {
        if element == value {
            return index
        }
    }
    return nil
}

3.泛型类型

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

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

    mutating func pop() -> Element {
        return items.removeLast()
    }
}
  • 泛型类型
class Box<T> {
    var value: T
    init(_ value: T) {
        self.value = value
    }
}
  • 泛型枚举
enum Result<Success, Failure> {
    case success(Success)
    case failure(Failure)
}

4.关联类型(Associated Types)— 协议中的泛型

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

  • 例 protocol Container
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]
    }
}
  • 例 protocol Stackable,使用关键字associatedtype+typealias
protocol Stackable {

    associatedtype Element    

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

class StringStack : Stackable {

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

5.where 语句:添加更复杂的约束

可用于:

  • 泛型函数
  • 泛型类型扩展
  • 协议扩展
func allItemsMatch<C1: Container, C2: Container>(_ a: C1, _ b: C2) -> Bool
where C1.Item == C2.Item, C1.Item: Equatable {
    if a.count != b.count { return false }

    for i in 0..<a.count {
        if a[i] != b[i] { return false }
    }

    return true
}

示例

我们写了一个工具方法,用于加载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")