Swift
是一门面向协议的语言,开发中我们已经充分享受到面向协议编程的便利,但Swift
相比OC
还有一个更重要的优势,那就是对函数式编程提供了强大的支持。其中
Swift
提供的高阶函数,常常用来作用于Array
、Set
、Dictionary
中的每一个元素,它的本质也是函数,具有以下有两个特点:
- 接受函数或者是闭包作为参数
- 返回值是一个函数或者是闭包
map函数
map
函数作用于Collection
中的每一个元素,然后返回一个新的Collection
假设一个
String
类型的Array
,要求将集合中的每一个元素都转为小写。常规代码会这样写:
let strings = ["HANK", "CAT", "Kody", "COOCI"]
var newStrings = [String]()
for ele in strings{
newStrings.append(ele.lowercased())
}
还可以使用
map
函数替代常规写法:
let strings = ["HANK", "CAT", "Kody", "COOCI"]
var newStrings = strings.map{ $0.lowercased() }
print(newStrings)
//输出以下内容
//["hank", "cat", "kody", "cooci"]
map
函数可以对数组中的每一个元素做一次处理,如同遍历的作用map
函数接受一个闭包作为参数, 然后它会遍历整个数组,并对数组中每一个元素执行闭包中定义的操作。 相当于对数组中的所有元素做了一个映射map
函数可以对一个集合类型的所有元素做一个映射操作map
函数不会修改实例值, 而是新建一个拷贝
查看
map
函数的定义:
public func map<T>(_ transform: (Output) -> T) -> Just<T>
在源码中打开
Sequence.swift
文件,找到map
函数的定义,它其实是Sequence
协议的扩展
/// - Parameter transform: A mapping closure. `transform` accepts an
/// element of this sequence as its parameter and returns a transformed
/// value of the same or of a different type.
/// - Returns: An array containing the transformed elements of this
/// sequence.
///
/// - Complexity: O(*n*), where *n* is the length of the sequence.
@inlinable
public func map<T>(
_ transform: (Element) throws -> T
) rethrows -> [T] {
let initialCapacity = underestimatedCount
var result = ContiguousArray<T>()
result.reserveCapacity(initialCapacity)
var iterator = self.makeIterator()
// Add elements up to the initial capacity without checking for regrowth.
for _ in 0..<initialCapacity {
result.append(try transform(iterator.next()!))
}
// Add remaining elements, if any.
while let element = iterator.next() {
result.append(try transform(element))
}
return Array(result)
}
上述源码,可以看出
map
函数本质是创建新的数组,对集合内的每个元素进行transform
操作(闭包表达式),然后返回新数组
flatMap函数
先来看一下
flatMap
函数的定义:
public func flatMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]
打开
SequenceAlgorithms.swift
文件,找到flatMap
函数的定义:
/// - Parameter transform: A closure that accepts an element of this
/// sequence as its argument and returns a sequence or collection.
/// - Returns: The resulting flattened array.
///
/// - Complexity: O(*m* + *n*), where *n* is the length of this sequence
/// and *m* is the length of the result.
@inlinable
public func flatMap<SegmentOfResult: Sequence>(
_ transform: (Element) throws -> SegmentOfResult
) rethrows -> [SegmentOfResult.Element] {
var result: [SegmentOfResult.Element] = []
for element in self {
result.append(contentsOf: try transform(element))
}
return result
}
上述源码,
flatMap
函数中的闭包参数,同样是Sequence
类型,但其返回类型为SegmentOfResult
在函数体的泛型定义中,
SegmentOfResult
的类型其实就是Sequence
。 而flatMap
函数返回的类型是SegmentOfResult.Element
的数组从函数的返回值来看,与
map
函数的区别在于flatMap
函数会将Sequence
中的元素进行“压平”,返回的类型会是Sequence
中元素类型的数组,而map
函数返回的是闭包返回类型T
的数组相比较
map
函数来说,flatMap
函数最主要的两个作用一个是“压平”,一个是过滤nil
值
案例1
二维数组“压平”
let nums = [[1, 2, 3], [4, 5, 6]]
var result1 = nums.map{$0}
var result2 = nums.flatMap{$0}
print("map:\(result1)")
print("flatMap:\(result2)")
//输出以下内容
//map:[[1, 2, 3], [4, 5, 6]]
//flatMap:[1, 2, 3, 4, 5, 6]
上述代码,使用
map
函数输出的还是二维数组,但使用flatMap
函数变成一维数组
案例2
过滤
nil
值
let strings = ["HANK", "CAT", "Kody", "COOCI", nil]
var result1 = strings.map{$0}
var result2 = strings.flatMap{$0}
print("map:\(result1)")
print("flatMap:\(result2)")
//输出以下内容
//map:[Optional("HANK"), Optional("CAT"), Optional("Kody"), Optional("COOCI"), nil]
//flatMap:["HANK", "CAT", "Kody", "COOCI"]
上述代码,使用
map
函数,数组内元素变为Optional
类型,同时nil
值也被保留。使用flatMap
函数,会把数组内元素进行Optional
解包,并且过滤nil
值
let number: String? = String(20)
let restult = number.map{Int($0)}
上述代码,
number
为String
可选类型,使用map
函数将其转为Int
类型,此时restult
类型变为两层可选类型,使用时需要解包两次
let number: String? = String(20)
let restult = number.flatMap{Int($0)}
同样的代码,使用
flatMap
函数,restult
依然保持为可选类型
打开
Optional.swift
文件,找到map
函数的定义:
/// - Parameter transform: A closure that takes the unwrapped value
/// of the instance.
/// - Returns: The result of the given closure. If this instance is `nil`,
/// returns `nil`.
@inlinable
public func map<U>(
_ transform: (Wrapped) throws -> U
) rethrows -> U? {
switch self {
case .some(let y):
return .some(try transform(y))
case .none:
return .none
}
}
上述源码,当进入
case .some
的代码分支,返回时又进行了.some()
的调用,增加了一层可选
找到
flatMap
函数的定义:
/// - Parameter transform: A closure that takes the unwrapped value
/// of the instance.
/// - Returns: The result of the given closure. If this instance is `nil`,
/// returns `nil`.
@inlinable
public func flatMap<U>(
_ transform: (Wrapped) throws -> U?
) rethrows -> U? {
switch self {
case .some(let y):
return try transform(y)
case .none:
return .none
}
}
上述源码,当进入
case .some
的代码分支,直接进行transform
操作,将结果返回
flatMap
函数依然会遍历数组的元素,并对这些元素执行闭包中定义的操作- 与
map
函数不同的是,它对最终的结果进行了所谓的“压平”操作。多维数组flatMap
之后,变成一维数组flatMap
函数返回后,数组的元素会过滤nil
值,同时它会把Optional
解包
compactMap函数
序列可选类型,在
Swift 4.1
之后,应该将flatMap
替换为compactMap
打开
SequenceAlgorithms.swift
文件,找到compactMap
函数的定义:
/// - Parameter transform: A closure that accepts an element of this
/// sequence as its argument and returns an optional value.
/// - Returns: An array of the non-`nil` results of calling `transform`
/// with each element of the sequence.
///
/// - Complexity: O(*m* + *n*), where *n* is the length of this sequence
/// and *m* is the length of the result.
@inlinable // protocol-only
public func compactMap<ElementOfResult>(
_ transform: (Element) throws -> ElementOfResult?
) rethrows -> [ElementOfResult] {
return try _compactMap(transform)
}
// The implementation of flatMap accepting a closure with an optional result.
// Factored out into a separate functions in order to be used in multiple
// overloads.
@inlinable // protocol-only
@inline(__always)
public func _compactMap<ElementOfResult>(
_ transform: (Element) throws -> ElementOfResult?
) rethrows -> [ElementOfResult] {
var result: [ElementOfResult] = []
for element in self {
if let newElement = try transform(element) {
result.append(newElement)
}
}
return result
}
}
上述源码,在
_compactMap
方法内,遍历元素过程中做了可选值绑定操作,然后进行append
let strings = ["HANK", "CAT", "Kody", "COOCI", nil]
var result = strings.compactMap{$0}
print(result)
//输出以下内容
//["HANK", "CAT", "Kody", "COOCI"]
上述代码,使用
compactMap
函数将nil
值过滤
- 当转换闭包返回可选值并且期望得到的结果为非可选值的序列时,使用
compactMap
函数- 当对于序列中元素,转换闭包返回的是序列或者集合时,而期望得到的结果是一维数组,使用
flatMap
函数
filter函数
filter
函数用于过滤元素,筛选出集合中满足某种条件的元素
let nums = [1, 2, 3, 4, 5]
let result = nums.filter{ $0 % 2 != 0 }
print(result)
//输出以下结果:
//[1, 3, 5]
上述代码,使用
filter函数
找到数组元素中的奇数
打开
Sequence.swift
文件,找到filter
函数的定义:
/// - Parameter isIncluded: A closure that takes an element of the
/// sequence as its argument and returns a Boolean value indicating
/// whether the element should be included in the returned array.
/// - Returns: An array of the elements that `isIncluded` allowed.
///
/// - Complexity: O(*n*), where *n* is the length of the sequence.
@inlinable
public __consuming func filter(
_ isIncluded: (Element) throws -> Bool
) rethrows -> [Element] {
return try _filter(isIncluded)
}
@_transparent
public func _filter(
_ isIncluded: (Element) throws -> Bool
) rethrows -> [Element] {
var result = ContiguousArray<Element>()
var iterator = self.makeIterator()
while let element = iterator.next() {
if try isIncluded(element) {
result.append(element)
}
}
return Array(result)
}
filter
函数将闭包表达式作为参数- 调用
ContiguousArray<Element>()
声明一个空集合- 使用
self.makeIterator()
生成一个迭代器- 通过
while
遍历元素- 将
isIncluded
(闭包表达式)作为判断条件,将符合条件的元素加入到集合中- 返回新数组
forEach函数
对于集合类型的元素,不必每次都通过for
循环来做遍历,Sequence
中同样提供了高阶函数以供使用:
let nums = [1, 2, 3, 4, 5]
nums.forEach {
print($0)
}
上述代码,通过
forEach
函数遍历数组元素进行打印
打开
Sequence.swift
文件,找到forEach
函数的定义:
/// Using the `forEach` method is distinct from a `for`-`in` loop in two
/// important ways:
///
/// 1. You cannot use a `break` or `continue` statement to exit the current
/// call of the `body` closure or skip subsequent calls.
/// 2. Using the `return` statement in the `body` closure will exit only from
/// the current call to `body`, not from any outer scope, and won't skip
/// subsequent calls.
///
/// - Parameter body: A closure that takes an element of the sequence as a
/// parameter.
@_semantics("sequence.forEach")
@inlinable
public func forEach(
_ body: (Element) throws -> Void
) rethrows {
for element in self {
try body(element)
}
}
}
上述源码,
forEach
函数的本质还是使用for...in
循环,执行闭包表达式
如果想获取当前元素的
index
,可以使用enumerated
函数
let nums = [1, 2, 3, 4, 5]
nums.enumerated().forEach {
print("index:\($0),emelent:\($1)")
}
//输出以下结果:
//index:0,emelent:1
//index:1,emelent:2
//index:2,emelent:3
//index:3,emelent:4
//index:4,emelent:5
上述代码,可以同时输出
index
和emelent
找到
emelent
函数的定义,返回一个EnumeratedSequence
类型
@inlinable public func enumerated() -> EnumeratedSequence<Array<Element>>
打开
Algorithm.swift
文件,找到EnumeratedSequence
定义:
@frozen
public struct EnumeratedSequence<Base: Sequence> {
@usableFromInline
internal var _base: Base
/// Construct from a `Base` sequence.
@inlinable
internal init(_base: Base) {
self._base = _base
}
}
EnumeratedSequence
是一个接收Sequence
类型的结构体
extension EnumeratedSequence {
@frozen
public struct Iterator {
@usableFromInline
internal var _base: Base.Iterator
@usableFromInline
internal var _count: Int
/// Construct from a `Base` iterator.
@inlinable
internal init(_base: Base.Iterator) {
self._base = _base
self._count = 0
}
}
}
Iterator
是EnumeratedSequence
内部用到的迭代器,_count
就是index
extension EnumeratedSequence.Iterator: IteratorProtocol, Sequence {
/// The type of element returned by `next()`.
public typealias Element = (offset: Int, element: Base.Element)
/// Advances to the next element and returns it, or `nil` if no next element
/// exists.
///
/// Once `nil` has been returned, all subsequent calls return `nil`.
@inlinable
public mutating func next() -> Element? {
guard let b = _base.next() else { return nil }
let result = (offset: _count, element: b)
_count += 1
return result
}
}
next
用来生成元素,返回一个元组类型。所以我们可以使用$0
获取index
,使用$1
获取emelent
reduce函数
reduce
函数可以对数组的元素进行计算reduce
函数可以将数组元素组合计算为一个值,并且会接受一个初始值,这个初始值的类型可以和数组元素类型不同- 将数组元素合并成字符串,元素之间可以指定分隔符号
let nums = [1, 2, 3, 4, 5]
let result = nums.reduce(10, +)
print(result)
//输出以下结果:
//25
上述代码,使用
reduce
函数将数组内元素和初始值10
,通过闭包表达式进行求和
打开
SequenceAlgorithms.swift
文件,找到reduce
函数的定义:
/// - Parameters:
/// - initialResult: The value to use as the initial accumulating value.
/// `initialResult` is passed to `nextPartialResult` the first time the
/// closure is executed.
/// - nextPartialResult: A closure that combines an accumulating value and
/// an element of the sequence into a new accumulating value, to be used
/// in the next call of the `nextPartialResult` closure or returned to
/// the caller.
/// - Returns: The final accumulated value. If the sequence has no elements,
/// the result is `initialResult`.
///
/// - Complexity: O(*n*), where *n* is the length of the sequence.
@inlinable
public func reduce<Result>(
_ initialResult: Result,
_ nextPartialResult:
(_ partialResult: Result, Element) throws -> Result
) rethrows -> Result {
var accumulator = initialResult
for element in self {
accumulator = try nextPartialResult(accumulator, element)
}
return accumulator
}
initialResult
:初始值nextPartialResult
:闭包表达式accumulator
:累加器遍历集合,将累加器和元素传入闭包表达式进行指定操作,将结果重新赋值给累加器,最后将累加器返回
案例1
对集合内每个元素进行
*2
操作
func customMap(collection: [Int], transform: (Int) -> Int) -> [Int]{
return collection.reduce([Int]()){
var arr: [Int] = $0
arr.append(transform($1))
return arr
}
}
let nums = [1, 2, 3, 4, 5]
let result = customMap(collection: nums){
$0 * 2
}
print(result)
//输出以下结果:
//[2, 4, 6, 8, 10]
上述代码,定义
customMap
方法
- 方法内部使用
reduce
函数,初始值为空数组- 闭包表达式内部将
$0
空数组赋值给arr
变量- 将
$1
元素传入transform
闭包表达式- 将执行结果
append
到arr
数组并进行返回
也可以通过
reduce
函数直接实现上述需求,由于传入的集合是可变数组,需要使用reduce(into:)
方法,否则编译报错
let nums = [1, 2, 3, 4, 5]
let result = nums.reduce(into: [Int]()){
$0.append($1 * 2)
}
print(result)
//输出以下结果:
//[2, 4, 6, 8, 10]
上述代码,使用
reduce(into:)
方法,也可以实现同样的效果
案例2
找出数组当中的最大值
let result = [1, 2, 3, 4, 5].reduce(0){
return $0 < $1 ? $1 : $0
}
print(result)
//输出以下结果:
//5
上述代码,要想找到最大值,一定会进行比较,这里的逻辑是通过累加器和元素进行比较
- 累加器初始传入
0
- 闭包表达式内,使用累加器和元素值比较
- 如果累加器小于元素值,将元素值赋值给累加器
- 循环往复,最终返回的累加器就是数组内元素的最大值
案例3
找到数组内奇数,再找到距离它最近的
2
的n
次方数,最后计算出总和
extension FixedWidthInteger {
@inlinable
func nextPowerOf2() -> Self {
guard self != 0 else {
return 1
}
return 1 << (Self.bitWidth - (self - 1).leadingZeroBitCount)
}
}
let result = [1, 2, 3, 4, 5].filter{
$0 % 2 != 0
}.map{
$0.nextPowerOf2()
}.reduce(0){
$0 + $1
}
print(result)
//输出以下结果:
//13
- 使用
filter
方法筛选出数组内奇数- 使用
map
方法将数组内元素依次通过nextPowerOf2
方法,找到距离元素值最近的2
的n
次方数- 使用
reduce
方法求和
案例4
逆序数组
let result = [1, 2, 3, 4, 5].reduce([Int]()){
return [$1] + $0
}
print(result)
//输出以下结果:
//[5, 4, 3, 2, 1]
- 累加器初始传入空数组
- 将元素包装成数组和累加器的数组相加,等同于两个数组元素合并
- 将合并后的数组赋值给累加器,直到所有元素合并完
LazySequence
假设我们有一个非常庞大的数组,想要通过
map
函数对每一个元素进行*2
,这将是一个非常耗时的操作
let numbers = Array(1...100000)
let mapNumbers = numbers.map{ $0 * 2 }
合理的逻辑,应该使用懒加载,在元素被访问的时候进行
*2
操作。但这样做还能使用map
函数吗?答案当然是可以的,
Swift
提供了⼀个lazy
关键字,它能实现这样的效果:
let numbers = Array(1...100000)
let mapNumbers = numbers.lazy.map{ $0 * 2 }
print(mapNumbers)
上述代码,直接输出
mapNumbers
,可以看到里面的元素没有发生任何变化
let numbers = Array(1...100000)
let mapNumbers = numbers.lazy.map{ $0 * 2 }
print(mapNumbers[0])
print(mapNumbers[1])
print(mapNumbers[2])
//输出以下结果:
//2
//4
//6
上述代码,当数组内指定元素被访问,这时该元素才会进行
*2
操作
通过上面的案例来看,当使⽤
Lazy
关键字后,得到的mapNumbers
保留的还是原来的数据,只不过在访问指定元素时,才会执⾏对应的闭包操作,产⽣新值给我们
当使用
lazy
关键字后,返回的是一个LazySequence
的结构体
查看
lazy
的定义:@inlinable public var lazy: LazySequence<Array<Element>> { get }
打开
LazySequence.swift
文件,找到LazySequenceProtocol
协议的定义:
public protocol LazySequenceProtocol: Sequence {
/// A `Sequence` that can contain the same elements as this one,
/// possibly with a simpler type.
///
/// - See also: `elements`
associatedtype Elements: Sequence = Self where Elements.Element == Element
/// A sequence containing the same elements as this one, possibly with
/// a simpler type.
///
/// When implementing lazy operations, wrapping `elements` instead
/// of `self` can prevent result types from growing an extra
/// `LazySequence` layer. For example,
///
/// _prext_ example needed
///
/// Note: this property need not be implemented by conforming types,
/// it has a default implementation in a protocol extension that
/// just returns `self`.
var elements: Elements { get }
}
LazySequenceProtocol
协议遵循了Sequence
协议
找到
lazy
的定义:
extension Sequence {
/// A sequence containing the same elements as this sequence,
/// but on which some operations, such as `map` and `filter`, are
/// implemented lazily.
@inlinable // protocol-only
public var lazy: LazySequence<Self> {
return LazySequence(_base: self)
}
}
lazy
是一个计算属性,返回一个LazySequence
对象
@frozen // lazy-performance
public struct LazySequence<Base: Sequence> {
@usableFromInline
internal var _base: Base
/// Creates a sequence that has the same elements as `base`, but on
/// which some operations such as `map` and `filter` are implemented
/// lazily.
@inlinable // lazy-performance
internal init(_base: Base) {
self._base = _base
}
}
LazySequence
初始化的时候,保留了当前的集合
extension LazySequence: Sequence {
public typealias Element = Base.Element
public typealias Iterator = Base.Iterator
@inlinable
public __consuming func makeIterator() -> Iterator {
return _base.makeIterator()
}
@inlinable // lazy-performance
public var underestimatedCount: Int {
return _base.underestimatedCount
}
@inlinable // lazy-performance
@discardableResult
public __consuming func _copyContents(
initializing buf: UnsafeMutableBufferPointer<Element>
) -> (Iterator, UnsafeMutableBufferPointer<Element>.Index) {
return _base._copyContents(initializing: buf)
}
@inlinable // lazy-performance
public func _customContainsEquatableElement(_ element: Element) -> Bool? {
return _base._customContainsEquatableElement(element)
}
@inlinable // generic-performance
public __consuming func _copyToContiguousArray() -> ContiguousArray<Element> {
return _base._copyToContiguousArray()
}
}
LazySequence
扩展,遵循Sequence
协议makeIterator()
方法创建迭代器
打开
Map.swift
文件,找到在LazySequenceProtocol
扩展中实现的map
函数
extension LazySequenceProtocol {
/// Returns a `LazyMapSequence` over this `Sequence`. The elements of
/// the result are computed lazily, each time they are read, by
/// calling `transform` function on a base element.
@inlinable
public func map<U>(
_ transform: @escaping (Element) -> U
) -> LazyMapSequence<Elements, U> {
return LazyMapSequence(_base: elements, transform: transform)
}
}
LazySequenceProtocol
扩展,实现map
函数- 返回一个
LazyMapSequence
对象
/// A `Sequence` whose elements consist of those in a `Base`
/// `Sequence` passed through a transform function returning `Element`.
/// These elements are computed lazily, each time they're read, by
/// calling the transform function on a base element.
@frozen
public struct LazyMapSequence<Base: Sequence, Element> {
public typealias Elements = LazyMapSequence
@usableFromInline
internal var _base: Base
@usableFromInline
internal let _transform: (Base.Element) -> Element
/// Creates an instance with elements `transform(x)` for each element
/// `x` of base.
@inlinable
internal init(_base: Base, transform: @escaping (Base.Element) -> Element) {
self._base = _base
self._transform = transform
}
}
LazyMapSequence
初始化的时候,保存了当前集合和transform
闭包表达式
extension LazyMapSequence.Iterator: IteratorProtocol, Sequence {
/// Advances to the next element and returns it, or `nil` if no next element
/// exists.
///
/// Once `nil` has been returned, all subsequent calls return `nil`.
///
/// - Precondition: `next()` has not been applied to a copy of `self`
/// since the copy was made.
@inlinable
public mutating func next() -> Element? {
return _base.next().map(_transform)
}
}
当访问指定元素时,会调用
next()
方法,然后对元素进行transform
操作
所以
Lazy
的本质,其实就是保存当前集合和对应的操作,然后在访问具体元素的时候,执⾏对应的操作