请注意,本文大部分内容摘自《Swift 进阶》。
什么是泛型?《Swift 进阶》中给出的定义是:泛型编程是一种可以保持类型安全性的代码重用技术。
泛型也被成为 参数化多态
。
实际上,就是让编译器为结构相同的代码提供模板,在编译时为不同的数据结构生成对应的方法。在 C++ 中,多态的关键字就是 template
。
比如,我们要实现最大值函数用于获取两个整数、两个浮点数、两个字符串中较大的值。
如果不借助泛型,我们需要为每一种数据结构单独定义一个 max
函数:
func max(_ a: Int, _ b: Int) -> Int {
return a > b ? a : b
}
func max(_ a: Float, _ b: Float) -> Float {
return a > b ? a : b
}
func max(_ a: String, _ b: String) -> String {
return a > b ? a : b
}
如果借助泛型,我们只需要定义一个 max
泛型函数,然后让每种数据结构遵循 Comparable
协议即可:
func max<T>(_ x: T, _ y: T) -> T where T : Comparable {
return x > y ? x : y
}
泛型通常都会和协议成对出现,因为 Swift 要通过协议明确约束泛型参数的行为。上面的 max
泛型函数就是一个很好的例子,泛型 T 需要遵守 Comparable
协议。
Swift 中的泛型也非常强大,快来深入了解一下吧~
函数和方法并不是唯一的泛型类型。我们还可以有泛型结构体,泛型类和泛型枚举。
比如,为 Swift 可选链提供支持的 Optional 枚举类型就是基于泛型设计的:
enum Optional<Wrapped> {
case none
case some(Wrapped)
}
我们甚至可以基于泛型定义递归的数据结构:
enum BinaryTree<Element> {
case leaf
indirect case node(Element, l: BinaryTree<Element>, r: BinaryTree<Element>)
}
如果您打开 Xcode 中的文档查看 数组(Array) 在 Swift 中的定义,您会看到它是基于泛型设计的 struct
:
struct Array<Element>
泛型的应用几乎贯穿了整个 Swift 标准库的实现:
→ Optional 用泛型参数抽象它包装的类型。
→ Result 有两个泛型参数:分别表示成功和失败这两种结果对应的值的类型。
→ Unsafe[Mutable]Pointer 用泛型参数表示指针指向的对象的类型。
→ Key paths 中使用了泛型表示根类型以及路径的值类型。
→ 各种表示范围的类型,使用了泛型表达范围的上下边界。
extension BinaryTree {
init(_ value: Element) {
self = .node(value, l: .leaf, r: .leaf)
}
}
extension BinaryTree {
var values: [Element] {
switch self {
case .leaf:
return []
case let .node(el, left, right): // 中序遍历
return left.values + [el] + right.values
}
}
}
其实,和扩展普通类型比较类似,只是我们要用泛型类型替换某个具体的类型。
泛型和 Any
的用途是类似的,不过二者有以下不同:
Any
缺少了类型安全性,需要使用反射/内省或者类型转换才可以确定实例的具体类型;Any
在运行时确定类型,前者可以有效地提高运行时的性能;请观察以下示例代码:
extension Array {
func reduce<Result>(_ initial: Result, _ combine: (Result, Element) -> Result) -> Result
}
从方法的签名中,我们便可大致理解其输入、输出和函数的主体功能。如果泛型遵循了协议,我们可以通过协议更好地理解参数的行为以及函数的作用效果。
作为对比,请观察以下 Any
版本的方法:
extension Array {
func reduce(_ initial: Any, _ combine: (Any, Any) -> Any) -> Any
}
我们很难知道输入和输入的参数的性质,也很难理解这些 Any
类型的参数会怎样参与函数的处理过程。
从某种意义上说,一个函数或方法的泛型程度越高,它能做的事情就越少。
请观察 Swift Array的 append 方法的签名:
func append<S>(contentsOf newElements: S) where S : Sequence, Element == S.Element
想要一次性地添加多个元素,这些元素需要被存储在一个序列中,且序列中的元素的类型与当前数组中元素的类型相同。
我们将重构一个非泛型的网络代码,使用泛型提取出其中公共的功能。
请观察以下示例代码:
struct User: Decodable {}
enum NetworkError: Error {
case noData
}
let webserviceURL = URL(string: "https://ficowshen.com")!
extension URLSession {
func loadUser(callback: @escaping (Result<User, Error>) -> ()) {
let userURL = webserviceURL.appendingPathComponent("/profile")
dataTask(with: userURL) { data, response, error in
callback(Result {
if let e = error { throw e }
guard let d = data else { throw NetworkError.noData }
return try JSONDecoder().decode(User.self, from: d)
})
}.resume()
}
}
我们可以把 User 替换成一个泛型参数,这段代码就不再依赖某个具体的 model 类型。
另外,还可以替换掉直接进行 JSON 解码的代码,给 load 添加另外一个参数: parse,让它负责解析 web service 返回的数据,并创建一个 A 类型的对象。
extension URLSession {
func load<A>(url: URL,
parse: @escaping (Data) throws -> A,
callback: @escaping (Result<A, Error>) -> ()) {
dataTask(with: url) { data, response, error in
callback(Result {
if let e = error { throw e }
guard let d = data else { throw NetworkError.noData }
return try parse(d)
})
}.resume()
}
}
接下来,我们就可以用这个泛型方法加载不同的请求了。
加载 User
:
let profileURL = webserviceURL.appendingPathComponent("profile")
URLSession.shared.load(url: profileURL, parse: {
try JSONDecoder().decode(User.self, from: $0)
}) { print($0) }
加载 BlogPost
:
struct BlogPost: Decodable {}
let postURL = webserviceURL.appendingPathComponent("blog")
URLSession.shared.load(url: postURL, parse: {
try JSONDecoder().decode(BlogPost.self, from: $0)
}) { print($0) }
由于 URL 和进行数据解码的 parse 函数总是成对出现的,一个更好的做法是把它们封装到一个 Resource 结构体中:
struct Resource<A> {
let url: URL
let parse: (Data) throws -> A
}
let profile = Resource<User>(url: profileURL, parse: {
try JSONDecoder().decode(User.self, from: $0)
})
let post = Resource<BlogPost>(url: postURL, parse: {
try JSONDecoder().decode(BlogPost.self, from: $0)
})
为了避免 JSONDecoder
代码的重复,可以给 Resource
添加一个便利初始化方法:
extension Resource where A: Decodable {
init(json url: URL) {
self.url = url
self.parse = { data in
try JSONDecoder().decode(A.self, from: data)
}
}
}
此时,就可以更精炼的定义表示 JSON 的资源:
let profile = Resource<User>(json: profileURL)
let blogPost = Resource<BlogPost>(json: postURL)
最后,为 URLSession
增加一个支持 Resource
的泛型方法:
extension URLSession {
func load<A>(_ r: Resource<A>,
callback: @escaping (Result<A, Error>) -> ()) {
dataTask(with: r.url) { data, response, err in
callback(Result {
if let e = err { throw e }
guard let d = data else { throw NetworkError.noData }
return try r.parse(d)
})
}.resume()
}
}
这样,就可以便捷地获取用户的信息了:
URLSession.shared.load(profile) { result in
print(result)
}
我们一般会基于泛型来构建通用数据结构,因为数据结构是完全抽象的,可以完全不依赖于具体内容的类型。
比如,Swift 标准库中不包含的队列:
struct Queue<Element> {
private var enqueueStack = [Element]()
private var dequeueStack = [Element]()
var first: Element? { dequeueStack.last }
var isEmpty: Bool { dequeueStack.isEmpty && enqueueStack.isEmpty }
var count: Int { dequeueStack.count + enqueueStack.count }
mutating func enqueue(_ element: Element) {
enqueueStack.append(element)
}
mutating func enqueue(contentsOf elements: [Element]) {
enqueueStack.append(contentsOf: elements)
}
mutating func dequeue() -> Element? {
if dequeueStack.isEmpty {
dequeueStack = enqueueStack.reversed()
enqueueStack.removeAll()
}
return dequeueStack.popLast()
}
}
以及优先队列(基于二叉堆实现):
struct PriorityQueue<T> {
var isEmpty: Bool { heapSize == 0 }
/// Returns the max value. To return min value, insert with the opposite numbers.
var top: T {
if isEmpty { fatalError() }
return heap[0]
}
var size: Int { heapSize }
private let degree = 2
private let sort: ((T, T) -> Bool)
private var heap = [T](), heapSize = 0
private var isFull: Bool { heapSize == heap.count }
init(capacity: Int, defaultValue: T, sort: @escaping ((T, T) -> Bool)) {
self.sort = sort
heap = [T](repeating: defaultValue, count: capacity)
}
/// O(log N)
mutating func insert(_ x: T) {
if isFull { assertionFailure("heap is full") }
heap[heapSize] = x
heapSize += 1
heapifyUp(heapSize - 1)
}
/// O(log N)
@discardableResult
mutating func pop() -> T { deleteAt(0) }
/// O(log N)
@discardableResult
mutating func deleteAt(_ index: Int) -> T {
if isEmpty { assertionFailure("heap is empty") }
let element = heap[index]
heap[index] = heap[heapSize - 1]
heapSize -= 1
heapifyDown(index)
return element
}
/// Adjusts the last number, move it up if it's bigger than its children.
/// - Parameter index: The end index of the heap
private mutating func heapifyUp(_ index: Int) {
var i = index
let insertValue = heap[i]
while i > 0, sort(insertValue, heap[parent(i)]) {
heap[i] = heap[parent(i)]
i = parent(i)
}
heap[i] = insertValue
}
/// Adjusts the first number, move it down if it's smaller than its children.
/// - Parameter index: The start index of the heap
private mutating func heapifyDown(_ index: Int) {
var i = index, child = 0, temp = heap[i]
while kthChild(i, 1) < heapSize {
child = nextChild(i)
if sort(temp, heap[child]) { break }
heap[i] = heap[child]
i = child
}
heap[i] = temp
}
private func parent(_ i: Int) -> Int {
return (i - 1) / degree
}
private func kthChild(_ i: Int, _ k: Int) -> Int {
return degree * i + k
}
private func nextChild(_ i: Int) -> Int {
let leftChild = kthChild(i, 1)
let rightChild = kthChild(i, 2)
return sort(heap[leftChild], heap[rightChild]) ? leftChild : rightChild
}
}
看官,您是否感觉到了泛型的强大了呢?
对于与类型无关的代码,我们可以用泛型将其抽象出来,并以此划分出更加清晰的责任边界。
响应式编程中的各种 operators 就是最好的例子,比如:map
, flatMap
, reduce
等等。
善用泛型,我们可以就有效地避免重复造轮子,最终提高代码的质量和减少项目维护的工作量。
阅读本书使我获益良多,衷心地感谢优秀的内容生产者们!
如需深入学习相关内容,推荐大家购买原版书籍 《Swift 进阶》!
请大家支持原创,用行动去鼓励高质量的内容生产者!
觉得不错?点个赞呗~
本文链接:Swift 进阶 —— 泛型
转载声明:本站文章如无特别说明,皆为原创。转载请注明:Ficow Shen's Blog