分类 Swift 下的文章

Swift高阶函数

高阶函数是指可以接收函数作为参数或者返回函数作为结果的函数。Swift 标准库提供了多个强大的高阶函数,主要用于集合类型的操作。以下是 Swift 中常用的高阶函数及其使用场景:

1. map(_:)

功能:对集合中的每个元素进行转换,返回一个新的数组。

使用场景:当你需要对数组中的每个元素进行相同操作并得到新数组时。

let numbers = [1, 2, 3, 4]
let squared = numbers.map { $0 * $0 }  // [1, 4, 9, 16]

2. compactMap(_:)

功能:类似于 map,但会自动过滤掉 nil 值。

使用场景:转换可能产生 nil 的情况,并希望自动去除 nil 值。

let strings = ["1", "2", "three", "4"]
let numbers = strings.compactMap { Int($0) }  // [1, 2, 4]

3. flatMap(_:)

功能:将二维数组"压平"为一维数组,或处理嵌套可选值。

使用场景

  • 处理嵌套数组
  • 处理嵌套可选值(Swift 4.1+ 中对于可选值推荐使用 compactMap)
let nestedArray = [[1, 2, 3], [4, 5, 6]]
let flattened = nestedArray.flatMap { $0 }  // [1, 2, 3, 4, 5, 6]

4. filter(_:)

功能:根据条件过滤集合中的元素。

使用场景:需要从集合中筛选符合条件的元素时。

let numbers = [1, 2, 3, 4, 5, 6]
let evens = numbers.filter { $0 % 2 == 0 }  // [2, 4, 6]

5. reduce(_:_:)

功能:将集合中的所有元素组合成一个值。

使用场景:需要计算总和、拼接字符串或任何需要将集合"缩减"为单个值的操作。

let numbers = [1, 2, 3, 4]
let sum = numbers.reduce(0) { $0 + $1 }  // 10
let product = numbers.reduce(1, *)  // 24

6. sorted(by:)

功能:根据条件对集合进行排序。

使用场景:需要自定义排序规则时。

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
let sortedNames = names.sorted(by: { $0 < $1 })  // 升序排列

7. forEach(_:)

功能:对集合中的每个元素执行操作。

使用场景:需要对每个元素执行操作但不需要返回值时(与 map 的区别)。

["a", "b", "c"].forEach { print($0) }

8. contains(where:)

功能:检查集合中是否包含满足条件的元素。

使用场景:需要检查集合中是否存在符合条件的元素时。

let numbers = [1, 2, 3, 4, 5]
let hasEven = numbers.contains(where: { $0 % 2 == 0 })  // true

9. first(where:)/last(where:)

功能:返回集合中第一个/最后一个满足条件的元素。

使用场景:需要查找集合中第一个或最后一个符合条件的元素时。

let numbers = [10, 20, 30, 40]
let firstMultipleOf15 = numbers.first(where: { $0 % 15 == 0 })  // 30

10. prefix(while:)/drop(while:)

功能

  • prefix(while:):从开头开始获取满足条件的元素,直到条件不满足
  • drop(while:):跳过开头满足条件的元素,返回剩余部分

使用场景:需要基于条件获取或跳过集合开头部分时。

let scores = [85, 90, 78, 92, 88, 76]
let goodScores = scores.prefix(while: { $0 >= 85 })  // [85, 90]
let afterFirstBad = scores.drop(while: { $0 >= 85 })  // [78, 92, 88, 76]

11. zip(_:_:)

功能:将两个序列组合成一个元组序列。

使用场景:需要同时遍历两个集合时。

let names = ["Alice", "Bob", "Charlie"]
let scores = [85, 92, 78]
for (name, score) in zip(names, scores) {
    print("\(name): \(score)")
}

使用建议

  1. 链式调用:可以组合多个高阶函数

    let result = (1...10)
        .filter { $0 % 2 == 0 }
        .map { $0 * $0 }
        .reduce(0, +)
  2. 性能考虑:每个高阶函数都会创建一个新集合,对于大数据集可能需要考虑性能
  3. 可读性:当逻辑复杂时,有时传统的 for 循环可能更易读

这些高阶函数使 Swift 代码更加简洁、表达力更强,是函数式编程风格的重要组成部分。

补充:flatMap 与 compactMap 的区别

compactMap 不会自动将二维数组降为一维数组,这是它与 flatMap 的一个重要区别。

主要区别

  1. compactMap:

    • 主要用途:在映射过程中过滤掉 nil
    • 不会自动"压平"二维数组
    • 返回的数组维度与输入相同
  2. flatMap (在 Swift 4.1 及以后版本):

    • 主要用途:将二维数组降为一维数组
    • 不处理 nil 值过滤(对于可选值过滤,改用 compactMap

示例对比

1. compactMap 行为

let nestedArray = [[1, nil, 3], [nil, 5, 6]]
let result = nestedArray.compactMap { $0 }
print(result) 
// 输出: [[Optional(1), nil, Optional(3)], [nil, Optional(5), Optional(6)]]
// 仍然是二维数组,只是对最外层数组进行了 nil 过滤(这里没有 nil 所以没变化)

2. 正确使用 compactMap 过滤 nil

let arrayWithOptionals: [Int?] = [1, nil, 3, nil, 5]
let nonNilValues = arrayWithOptionals.compactMap { $0 }
print(nonNilValues) // 输出: [1, 3, 5]

3. flatMap 降维示例

let nestedArray = [[1, 2, 3], [4, 5, 6]]
let flattened = nestedArray.flatMap { $0 }
print(flattened) // 输出: [1, 2, 3, 4, 5, 6]

历史变化

  • Swift 4.1 之前flatMap 有三种功能:

    1. 二维数组降维
    2. 过滤 nil (类似现在的 compactMap)
    3. 组合操作
  • Swift 4.1 及以后

    • flatMap 只保留降维功能
    • 过滤 nil 的功能迁移到 compactMap

如何实现带 nil 过滤的降维

如果需要同时处理二维数组降维和过滤 nil,可以组合使用:

let nestedWithOptionals = [[1, nil, 3], [nil, 5, 6]]
let result = nestedWithOptionals.flatMap { $0 }.compactMap { $0 }
print(result) // 输出: [1, 3, 5, 6]

autoreleasepool 深度解析

什么是 autoreleasepool

autoreleasepool(自动释放池)是 Objective-C 和 Swift 中用于延迟对象释放的一种内存管理机制。它允许你将对象的释放操作"延迟"到池被销毁时,而不是立即执行。

底层数据结构

自动释放池是一个结构体对象,在底层维护一个以栈为节点的双向链表,它是有所属的线程的。

AutoreleasePoolPage 类,它维护一个栈结构,自动释放池维护多个page为节点的双向链表。

class AutoreleasePoolPage {
        magic_t const magic;
        id *next; // 指向栈的下一个空闲地址
        pthread_t const thread; //所属线程
        AutoreleasePoolPage * const parent; //指向链表前一个节点
        AutoreleasePoolPage *child; //指向链表后一个节点
        uint32_t const depth;
        uint32_t hiwat;
};

AutoreleasePoolPage 类(每个池由多个 Page 组成)

  • 每个 Page 大小为 4096 字节(一页内存)
  • 以双向链表形式连接
  • 包含以下关键部分:

    • magic:校验字段
    • next:指向下一个空闲位置
    • thread:所属线程
    • parentchild:构成双向链表

工作原理

入栈过程(添加对象)

  1. 当对象调用 autorelease 时:

    [obj autorelease]; // 相当于压栈
  2. 系统会获取当前线程的"hot page"(活跃页面)
  3. 将对象地址存入 next 指向的位置,然后 next++

出栈过程(释放对象)

  1. 当池销毁时(objc_autoreleasePoolPop):
  2. 从当前页面的 next 指针开始向前遍历
  3. 对每个对象发送 release 消息
  4. 重置 next 指针位置

多页面管理

当单个页面空间不足时,系统会:

  1. 创建新的 AutoreleasePoolPage 作为 child
  2. 新页面成为新的"hot page"
  3. 形成双向链表结构:
[Page1] <--> [Page2] <--> [Page3]
  ↑             ↑           ↑ 
parent        parent      (最新页面)

为什么说是栈结构?

尽管物理实现是链表,但从行为上看是栈:

  • 压栈autorelease 操作相当于 push
  • 弹栈pool pop 操作相当于批量 pop
  • 嵌套池:外层池在内层池销毁后依然有效

线程局部存储

每个线程有自己独立的自动释放池栈:

  • 通过 thread 字段关联特定线程
  • TLS(Thread Local Storage)存储当前页指针

实际内存布局示例

假设有3个对象被 autorelease:

AutoreleasePoolPage:
+---------------+ 
| magic         |
| next ───────┐ |
| thread      | |
| parent      | |
| child       | |
| depth       | |
| hiwat       | |
+---------------+ 
| obj1        | ← next 最初指向这里
| obj2        |
| obj3        | ← next 现在指向这里
| ...         |

这种设计既保持了栈的逻辑特性,又通过链表实现了动态扩容,是工程实践中经典的"逻辑栈-物理链表"实现方式。

处理大型数据时的作用

处理大型数据(如循环中创建大量临时对象)时,autoreleasepool 可以:

  1. 及时释放内存

    for i in 0..<100000 {
        autoreleasepool {
            let tempImage = processLargeImage(at: i) // 临时大对象
            // 使用tempImage...
        } // 这里tempImage会被立即释放
    }
  2. 避免内存峰值

    • 没有 autoreleasepool:所有临时对象会累积直到循环结束
    • 使用 autoreleasepool:每次迭代后立即释放

实际应用场景

  1. 主线程

    • RunLoop 每次循环会自动创建和销毁自动释放池
    • 所以主线程通常不需要手动管理
  2. 子线程

    DispatchQueue.global().async {
        autoreleasepool {
            // 处理大量数据
            let data = loadHugeData()
            process(data)
        } // 数据在这里被释放
    }
  3. Swift 与 Objective-C 交互

    • 纯 Swift 代码通常不需要 autoreleasepool
    • 但当调用 Objective-C API 返回自动释放对象时仍然有用

性能考虑

  1. 创建代价

    • 创建和销毁池本身有开销
    • 只应在处理大量临时对象时使用
  2. 最佳实践

    // 好:处理大量迭代时使用
    for item in hugeCollection {
        autoreleasepool {
            process(item)
        }
    }
    
    // 不好:单个小对象没必要
    autoreleasepool {
        let smallObject = createObject()
        use(smallObject)
    }

通过理解 autoreleasepool 的底层机制,我们可以在处理内存密集型操作时更精确地控制内存使用,避免不必要的内存增长。