阅读本文需要对 Swift 基本语法和泛型有基本的了解。
本文约840字,阅读大概需要 10 min。
收获:对 Swift 中的泛型使用更加深入。
尽量使用泛型来替代 Any
假设,在项目中我们需要实现一个 Stack 的数据结构,并且想支持任意基本类型。那么,我们可以使用 Any 实现下面的代码:
struct Stack {
var data = [Any]()
init(data: [Any]) {
self.data = data
}
mutating func pop() {
data.removeLast()
}
mutating func push(element: Any) {
data.append(element)
}
}
var stack = Stack(data: ["swift", "objective-c"])
stack.push(element: "iOS")
上述代码可以满足我们的需求,它可以支持任意类型。但是这种写法在使用中还是有很多缺点的:
- 使用元素时需要解包、转类型
print(stack.data.first as! String)
- 因为是 Any,所以可以赋值任意类型,导致代码可读性和健壮性不高
stack.push(element: 13)
//Error - Could not cast value of type 'Swift.Int' (0x10b4d7070) to 'Swift.String' (0x10b4d9190)
print(stack.data.last as! String)
所以,遇到此类需求时,我们应该使用泛型来代替上面的 Any。
struct Stack<Element> {
var data = [Element]()
init(data: [Element]) {
self.data = data
}
mutating func pop() {
data.removeLast()
}
mutating func push(element: Element) {
data.append(element)
}
}
var stack = Stack(data: ["swift", "objective-c"])
stack.push(element: "iOS")
- 只需解包无须转变类型
print(stack.data.first!)
- 使用时已确定类型,赋值别的类型直接报错,从而提高代码的健壮性
// 编译不成功 - Cannot convert value of type 'Int' to expected argument type 'String'
stack.push(element: 13)
通过 泛型 和 Extension 实现通用算法
接着上面的例子,假如我们想在 Stack 的基础上,添加一个计算栈内所有数字类型元素的和,我们可以用以下代码实现:
extension Stack where Element: Numeric {
func sum() -> Element {
var total: Element = 0
for num in data {
total += num
}
return total
}
}
var numStack = Stack(data: [1, 2])
numStack.sum() // 3
// strStack 是无法调用 sum 函数的,因为String 并没有遵守 Numberic 协议
var strStack = Stack(data: ["Swift", "iOS13"])
extension Stack where Element: Numeric
的意思是指:只有 Stack 的元素遵守 Numberic 协议,才能调用 sum() 函数。这么做是因为非数字类型是无法进行累加操作的。如果我们不添加该限制的话,total += num
这句代码会报错:Binary operator '+=' cannot be applied to two 'Element' operands
。
通过泛型 和 Extension 为 Protocol 扩展方法
map(系统标准库)
我们可以通过 泛型 + Extension 为 Protocol 扩展兼容多类型的方法,比如标准库提供的 map 函数,它的实现就是借助 泛型 + Extension 实现的:
extension Collection {
func map<T>(_ transform: (Element) -> T) -> [T] {
var result = [T]()
result.reserveCapacity(self.count)
var position = startIndex
while position != endIndex {
result.append(transform(self[position]))
position = index(after: position)
}
return result
}
}
因为在调用 map() 函数时,我们可以确定参数计算出 result 的大小,所以我们可以使用 result.reserveCapacity(self.count)
来优化代码效率。关于 reserveCapacity
更加详细的内容,请参见 Swift 中的 Array 性能比较: append vs reserveCapacity(译)
shuffle(系统标准库)
Fisher–Yates shuffle
算法是一个用来将一个有限集合生成一个随机排列的算法(数组随机排序)。
extension RandomAccessCollection where Self: MutableCollection {
mutating func shuffle() {
let n = count
guard n > 1 else { return }
for (i, pos) in indices.dropLast().enumerated() {
let otherPos = index(startIndex, offsetBy: Int.random(in: i..<n))
swapAt(pos, otherPos)
}
}
}
var arr = [1,2,3,4,5,6,7]
arr.shuffle()
//[4, 3, 5, 1, 6, 7, 2]
print(arr)