泛型函数:让代码从"具体类型"升维到"抽象类型"
- 场景:写了一个交换两个 Int 的函数,后来又要交换 Double、String、CGPoint......
- 泛型版本:
swift
/// 交换任意两个相同类型的值
/// T 是占位符,调用时由编译器推断
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
(a, b) = (b, a)
}
var x = 3.14, y = 2.71
swapTwoValues(&x, &y) // Double
print(x,y)
var s1 = "A", s2 = "B"
swapTwoValues(&s1, &s2) // String
print(s1,s2)
- 泛型约束:只支持"可比较"类型
swift
/// 返回数组中最大元素,数组为空时返回 nil
/// T 必须实现 Comparable 协议
func maxElement<T: Comparable>(in array: [T]) -> T? {
guard var max = array.first else { return nil }
for e in array.dropFirst() where e > max { max = e }
return max
}
print(maxElement(in: [3, 1, 4, 2])) // Int?
print(maxElement(in: ["apple", "zebra"])) // String?
- 多占位符 + 多个约束
swift
/// 把字典的 value 映射成新类型,key 不变
func mapValues<K, V, U>(
_ dict: [K: V],
_ transform: (V) throws -> U
) rethrows -> [K: U] {
try dict.mapValues(transform) // 直接复用标准库
}
易错点:
- 泛型函数仍然遵循"单态化"(monomorphization) 编译模型,不会带来运行时开销。
- 如果约束过多,考虑使用
where
子句可读性更好。
函数重载:同名不同参,编译器如何选?
- 重载维度
维度 | 能否作为重载依据 |
---|---|
参数个数 | ✅ |
参数类型 | ✅ |
参数标签 | ✅ |
返回类型 | ❌(不能单独作为依据) |
- 示例:看似相同,实则都能共存
swift
/// 1. 只有个数不同
func f(_ x: Int) { }
func f(_ x: Int, _ y: Int) { }
/// 2. 类型不同
func f(_ x: String) { }
/// 3. 标签不同
func f(value x: Int) { }
- 歧义爆发点:默认参数 + 可变参
swift
func sum(_ nums: Int...) -> Int { nums.reduce(0, +) }
func sum(_ a: Int, _ b: Int = 0) -> Int { a + b }
// 以下调用会编译失败:编译器无法决定用哪个
// sum(1)
解决:
- 把"可变参版本"改成内部判断空数组;
- 或者干脆改名,避免重载。
- 泛型 vs 重载
泛型是"横向"抽象,重载是"纵向"展开。当两者冲突时,编译器优先选"更具体"的重载版本:
swift
func printIt<T>(_ x: T) { print("generic: \(x)") }
func printIt(_ x: Int) { print("Int: \(x)") }
printIt(42) // 输出 Int: 42
printIt("hi") // 输出 generic: hi
递归:自己调用自己,如何不爆栈?
- 经典例子:阶乘
swift
/// 普通递归,深度大时可能栈溢出
func factorial(_ n: Int) -> Int {
n <= 1 ? 1 : n * factorial(n - 1)
}
- 尾递归(Tail Recursion)------让编译器做尾调用优化(TCO)
swift
/// 把中间结果放到 accumulator 参数里,递归调用是最后一条指令
func factorialT(_ n: Int, _ acc: Int = 1) -> Int {
n <= 1 ? acc : factorialT(n - 1, acc * n)
}
注意:
- Swift 5 之后不保证在
-O
优化下一定做 TCO,仅"尽力而为"。 - 真正要防爆栈,请用循环或
Sequence
惰性计算。
- 实战:递归遍历嵌套文件夹(简化版)
swift
import Foundation
/// 返回目录下所有 `.swift` 文件
func swiftFiles(in path: String) -> [String] {
let fm = FileManager.default
guard let enumerator = fm.enumerator(atPath: path) else { return [] }
var result: [String] = []
while let file = enumerator.nextObject() as? String {
if file.hasSuffix(".swift") {
result.append((path as NSString).appendingPathComponent(file))
}
}
return result
}
(递归由 FileManager
的 enumerator
代劳,无需自写)
函数式思维落地:再封装 map / filter / reduce
- reduce 的陷阱:初始值类型
swift
let nums = [1, 2, 3]
// 错误:拼接字符串却给 Int 初始值
// let r = nums.reduce(0) { $0 + String($1) } // 编译失败
// 正确
let r = nums.reduce("") { $0 + String($1) } // "123"
print(r)
- 封装"管道"算子,让代码像搭积木
swift
infix operator |> : AdditionPrecedence
/// 把值通过函数"管道"传递
func |> <T, U>(value: T, function: (T) -> U) -> U {
function(value)
}
/// 使用
let result = [1, 2, 3]
|> { $0.map { $0 * 2 } } // [2, 4, 6]
|> { $0.filter { $0 > 3 } } // [4, 6]
|> { $0.reduce(0, +) } // 10
print(result)
- 自定义"链式"集合封装
swift
struct Chain<Wrapped> {
private let value: Wrapped
init(_ value: Wrapped) { self.value = value }
func map<T>(_ transform: (Wrapped) -> T) -> Chain<T> {
.init(transform(value))
}
func filter(_ condition: (Wrapped) -> Bool) -> Chain<Wrapped>? {
condition(value) ? self : nil
}
func unwrap() -> Wrapped { value }
}
/// 使用
let ans = Chain([1, 2, 3, 4])
.map { $0.map { $0 * 10 } } // [10, 20, 30, 40]
.filter { !$0.isEmpty }!
.unwrap()
.reduce(0, +) // 100
print(ans)
常见坑位 Top 3
坑 | 现象 | 根治方案 |
---|---|---|
1. 可变参 + 默认参重载 | 编译歧义 | 改名或删除一个版本 |
2. 递归无终止条件 | 运行时崩溃 | 用 guard 提前 return |
3. 泛型约束过多 | 编译耗时飙升 | 把复杂约束拆成 protocol + extension |
结语
函数不仅是"代码片段",更是 Swift 世界里的"乐高积木"。
当你能把"泛型 + 重载 + 递归 + 函数式"自由组合时,就拥有了"用函数生产函数"的元编程能力。
愿我们都能把"函数"玩成"艺术",而不是"语法"。