码迷,mamicode.com
首页 > 其他好文 > 详细

闭包 -> map / floatMap / filter / reduce 浅析

时间:2016-07-06 16:37:46      阅读:650      评论:0      收藏:0      [点我收藏+]

标签:

原创: 转载请注明出处

 

闭包是自包含的函数代码块,可以在代码中被传递和使用

 

闭包可以捕获存储其所在上下文中任意常量和变量的引用。这就是所谓的闭合并包裹着这些常量变量,俗称闭包。Swift 会为您管理在捕获过程中涉及到的所有内存操作。

 

闭包表达式是一种利用简洁语法构建内联闭包的方式

 

sort 方法(The Sort Method

Swift 标准库提供了名为sort的方法,会根据您提供的用于排序的闭包函数将已知类型数组中的值进行排序。一旦排序完成,sort(_:)方法会返回一个与原数组大小相同,包含同类型元素且元素已正确排序的新数组。原数组不会被sort(_:)方法修改。

 

sort(_:)方法接受一个闭包,该闭包函数需要传入与数组元素类型相同的两个值,并返回一个布尔类型值来表明当排序结束后传入的第一个参数排在第二个参数前面还是后面。如果第一个参数值出现在第二个参数值前面,排序闭包函数需要返回true,反之返回false。

 

该例子对一个String类型的数组进行排序,因此排序闭包函数类型需为(String, String) -> Bool

提供排序闭包函数的一种方式是撰写一个符合其类型要求的普通函数,并将其作为sort(_:)方法的参数传入:

 

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

func backwards(s1: String, s2: String) -> Bool {

    return s1 > s2

}

var reversed = names.sort(backwards)

 

闭包表达式版本

reversed = names.sort({(s1: String, s2: String) -> Bool in

    return s1 > s2

})

该例中sort(_:)方法的整体调用保持不变,一对圆括号仍然包裹住方法的整个参数。然而,参数现在变成了内联闭包。而不再是函数。

 

 

闭包表达式语法(Closure Expression Syntax

闭包表达式语法有如下一般形式:

{ (parameters) -> returnType in

    statements

}

 

闭包表达式语法可以使用常量、变量和inout类型作为参数,不能提供默认值。也可以在参数列表的最后使用可变参数。元组也可以作为参数和返回值。

 

闭包的函数体部分由关键字in引入。该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始

 

 

根据上下文推断类型(Inferring Type From Context

因为所有的类型都可以被正确推断,返回箭头(->)和围绕在参数周围的括号也可以被省略:

reversed = names.sort( { s1, s2 in return s1 > s2 } )

 

实际上任何情况下,通过内联闭包表达式构造的闭包作为参数传递给函数或方法时,都可以推断出闭包的参数和返回值类型。 这意味着闭包作为函数或者方法的参数时,您几乎不需要利用完整格式构造内联闭包。

 

 

单表达式闭包隐式返回(Implicit Return From Single-Expression Clossures

单行表达式闭包可以通过省略return关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:

reversed = names.sort( { s1, s2 in s1 > s2 } )

在这个例子中,sort(_:)方法的参数类型明确了闭包必须返回一个Bool类型值。因为闭包函数体只包含了一个单一表达式(s1 > s2),该表达式返回Bool类型值,因此这里没有歧义,return关键字可以省略。

 

 

参数名称缩写(Shorthand Argument Names

Swift 自动为内联闭包提供了参数名称缩写功能,您可以直接通过$0,$1,$2来顺序调用闭包的参数,以此类推。

如果您在闭包表达式中使用参数名称缩写,您可以在闭包参数列表中省略对其的定义,并且对应参数名称缩写的类型会通过函数类型进行推断。in关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:

reversed = names.sort{ $0 > $1 }   )

在这个例子中,$0和$1表示闭包中第一个和第二个String类型的参数。

 

运算符函数(Operator Functions

 

实际上还有一种更简短的方式来撰写上面例子中的闭包表达式。Swift String类型定义了关于大于号(>)的字符串实现,其作为一个函数接受两个String类型的参数并返回Bool类型的值。而这正好与sort(_:)方法的参数需要的函数类型相符合。因此,您可以简单地传递一个大于号,Swift 可以自动推断出您想使用大于号的字符串函数实现:

reversed = names.sort(>)

 

 

 

尾随闭包(Trailing Closures

如果您需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性

 

func someFunctionThatTakesAClosure(closure: () -> Void) {

    // 函数体部分

}

 

// 以下是不使用尾随闭包进行函数调用

someFunctionThatTakesAClosure({

    // 闭包主体部分

})

 

// 以下是使用尾随闭包进行函数调用

someFunctionThatTakesAClosure() {

    // 闭包主体部分

}

 

捕获值(Capturing Values

闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。

 

注意

为了优化,如果一个值是不可变的,Swift 可能会改为捕获并保存一份对值的拷贝。

Swift 也会负责被捕获变量的所有内存管理工作,包括释放不再需要的变量。

 

 

非逃逸闭包(Nonescaping Closures)

当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注@noescape,用来指明这个闭包是不允许“逃逸”出这个函数的。将闭包标注@noescape能使编译器知道这个闭包的生命周期(译者注:闭包只能在函数体中被执行,不能脱离函数体执行,所以编译器明确知道运行时的上下文),从而可以进行一些比较激进的优化。

func someFunctionWithNoescapeClosure(@noescape closure: () -> Void) {

    closure()

}

举个例子,sort(_:)方法接受一个用来进行元素比较的闭包作为参数。这个参数被标注了@noescape,因为它确保自己在排序结束之后就没用了。

一种能使闭包逃逸出函数的方法是,将这个闭包保存在一个函数外部定义的变量中。举个例子,很多启动异步操作的函数接受一个闭包参数作为 completion handler。这类函数会在异步操作开始之后立刻返回,但是闭包直到异步操作结束后才会被调用。在这种情况下,闭包需要“逃逸”出函数,因为闭包需要在函数返回之后被调用。例如:

var completionHandlers: [() -> Void] = []

func someFunctionWithEscapingClosure(completionHandler: () -> Void) {

    completionHandlers.append(completionHandler)

}

someFunctionWithEscapingClosure(_:)函数接受一个闭包作为参数,该闭包被添加到一个函数外定义的数组中。如果你试图将这个参数标注为@noescape,你将会获得一个编译错误。

将闭包标注为@noescape使你能在闭包中隐式地引用self。

class SomeClass {

    var x = 10

    func doSomething() {

        someFunctionWithEscapingClosure { self.x = 100 }

        someFunctionWithNoescapeClosure { x = 200 }

    }

}

 

let instance = SomeClass()

instance.doSomething()

print(instance.x)

// prints "200"

 

completionHandlers.first?()

print(instance.x)

// prints "100"

 

闭包引起的循环强引用

1.循环强引用是在两个类实例属性互相保持对方的强引用时产生的,还知道了如何用弱引用和无主引用来打破这些循环强引用

 

2.循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了这个类实例时。这个闭包体中可能访问了实例的某个属性,例如self.someProperty,或者闭包中调用了实例的某个方法,例如self.someMethod()。这两种情况都导致了闭包捕获”self,从而产生了循环强引用。

 

本质是:两个强引用让彼此一直有效

 

闭包捕获列表(closure capture list

 

定义捕获列表

捕获列表中的每一项都由一对元素组成,一个元素是weak或unowned关键字,另一个元素是类实例的引用(例如self)或初始化过的变量(如delegate = self.delegate!)。这些项在方括号中用逗号分开。

如果闭包有参数列表和返回类型,把捕获列表放在它们前面:

lazy var someClosure: (Int, String) -> String = {

    [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in

    // 这里是闭包的函数体

}

如果闭包没有指明参数列表或者返回类型,即它们会通过上下文推断,那么可以把捕获列表和关键字in放在闭包最开始的地方:

lazy var someClosure: Void -> String = {

    [unowned self, weak delegate = self.delegate!] in

    // 这里是闭包的函数体

}

 

弱引用和无主引用

在闭包和捕获的实例总是互相引用并且总同时销毁时,将闭包内的捕获定义为无主引用

 

相反的,在被捕获的引用可能会变为nil时,将闭包内的捕获定义为弱引用。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为nil。这使我们可以在闭包体内检查它们是否存在。

 

注意
如果被捕获的引用绝对不会变为nil,应该用无主引用,而不是弱引用。

 

 

 

map,filter,reduce

 

 

关于SwiftString 、数组 、字典的基本用法这里就不再赘述了,这些都很简单 不会的 在用得时候baidu下就行了。这里主要看下这几个高阶函数

map

map方法,其获取一个闭包表达式作为其唯一参数 数组中的每一个元素调用一次该闭包函数,并返回该元素所映射的值(也可以是不同类型的值) 具体的映射方式和返回值类型由闭包来指定。

当提供给数组闭包函数后,map方法将返回一个新的数组,数组中包含了与原数组一一对应的映射后的值。

来看看map的定义 func map(transform: (T) -> U) -> U[] ,这里 T 和 U 都是泛型 ,指一种类型 , T 和U 只两个不同的类型 ,也可以相同

来看个例子
我们用一个Int类型数组存储商品金额,想把每个金额前面添加一个字符“¥

let prices = [10,20,30]

let strPrices = prices.map { "¥\($0)" }  

 

这个语法大家应该不陌生吧 ,陌生的去把上节闭包重新看一遍因为map只有一个参数,后面直接用了闭包的尾随 ,不会加括号了

得到的结果 :print(strPrices) //[¥10, ¥20, ¥30]

这只是一个简单的实例 ,其实你可以对每个元素进行很复杂的运算,这里不再赘述 ,用法如此,点到为止 、哈哈

filter

filter 顾名思义 就是用来过滤的 它使用来选择数组中满足条件的元
定义:filter(includeElement: (T) -> Bool) -> T[]
接受一个数组元素 返回一个Bool类型

let p = [10,20,33,44,87,15]

let res = p.filter{ $0>20 }

 

得到结果 :print(res) //[33, 44, 87]
是不是用起来很方便呀!!

reduce

reduce方法把数组元素组合计算为一个值,并且会接受一个初始值,这个初始值得类型可能和数组元素类型不同。来看定义reduce(initial: U, combine: (U, T) -> U) -> U
不同的大写字母当成不同的类型看待。来看实例

let p1 = [20,20,10]

let sum = p1.reduce(0) { $0+$1 }

print(sum) //50

 

这个实例给了个初始为Int类型的 0 ,用这个 0 去加数组所有元素

let sum2 = p1.reduce(4) { $0+$1 }

print(sum2) //54

 

初始值换成4 就会得到这样的结果 ,是不是也不是那么难理解呀!

let sum1 = p1.reduce("2") { " \($0) , \($1)" }

print(sum1) //  2 , 20 , 20 , 10

 

如果初始值是一个字串就不能用加法了。不过可以拼接得到上面的结果

map和filter都很好理解 ,reduce稍微难理解一点

需要说明的是数据比较大的时候,高阶函数会比传统实现更快,因为它可以并行执行(如运行在多核上),除非真的需要更高定制版本的map,reduce和filter,否则可以一直使用它们以获得更快的执行速度

 

 

 

 

Swift是一门支持函数式编程的语言,拥有MapFlatMap,Filter,Reduce针对集合类型的操作。在使用Objective-C开发时,如果你没接触过函数式编程,那你可能没听说过这些名词,希望此篇文章可以帮助你了解Swift中的MapFlatMap,Filter,Reduce

Map

首先我们来看一下mapSwift中的的定义,我们看到它可以用在 OptionalsSequenceType 上(如:数组、词典等)。

public enum Optional<Wrapped> : _Reflectable, NilLiteralConvertible {
    /// If `self == nil`, returns `nil`.  Otherwise, returns `f(self!)`.
    @warn_unused_result
    public func map<U>(@noescape f: (Wrapped) throws -> U) rethrows -> U?
}

extension CollectionType {
    /// Returns an `Array` containing the results of mapping `transform`
    /// over `self`.
    ///
    /// - Complexity: O(N).
    @warn_unused_result
    public func map<T>(@noescape transform: (Self.Generator.Element) throws -> T) rethrows -> [T]
}

@warn_unused_result:表示如果没有检查或者使用该方法的返回值,编译器就会报警告。
@noescape:表示transform这个闭包是非逃逸闭包,它只能在当前函数map中执行,不能脱离当前函数执行。这使得编译器可以明确的知道运行时的上下文环境(因此,在非逃逸闭包中可以不用写self),进而进行一些优化。

Optionals进行map操作

简要的说就是,如果这个可选值有值,那就解包,调用这个函数,之后返回一个可选值,需要注意的是,返回的可选值类型可以与原可选值类型不一致:

///原来类型: Int?,返回值类型:String?
var value:Int? = 1
var result = value.map { String("result = \($0)") }
/// "Optional("result = 1")"
print(result)
var value:Int? = nil
var result = value.map { String("result = \($0)") }
/// "nil"
print(result)
SequenceType进行map操作

我们可以使用map方法遍历数组中的所有元素,并对这些元素一一进行一样的操作(函数方法)。map方法返回完成操作后的数组。

技术分享
 


我们可以用For-in完成类似的操作:

var values = [1,3,5,7]
var results = [Int]()
for var value in values {
    value *= 2
    results.append(value)
}
//"[2, 6, 10, 14]"
print(results)

这看起来有点麻烦,我们得先定义一个变量var results然后将values里面的元素遍历,进行我们的操作以后,将其添加进results,我们比较下使用map又会怎么样:

let results = values.map ({ (element) -> Int in
    return element * 2
})
//"[2, 6, 10, 14]"

我们向map传入了一个闭包,对数组中的所有元素都 乘以2,将返回的新的数组赋值为results,是不是精简了许多?还能更精简!

精简写法

let results = values.map { $0 * 2 }
//"[2, 6, 10, 14]"

what the fuck...沉住气,让我们一步步来解析怎么就精简成这样了,保证让你神清气爽。翻开The Swift Programming Language中对于闭包的定义你就能找到线索。

第一步:

由于闭包的函数体很短,所以我们将其改写成一行:

let results = values.map ({ (element) -> Int in return element * 2 })
//"[2, 6, 10, 14]"
第二步:

由于我们的闭包是作为map的参数传入的,系统可以推断出其参数与返回值,因为其参数必须是(Element) -> Int类型的函数。因此,返回值类型,->及围绕在参数周围的括号都可以被忽略:

let results = values.map ({ element  in return element * 2 })
//"[2, 6, 10, 14]"
第三步:

单行表达式闭包可以通过省略return来隐式返回闭包的结果:

let results = values.map ({ element  in element * 2 })
//"[2, 6, 10, 14]"

由于闭包函数体只含有element * 2这单一的表达式,该表达式返回Int类型,与我们例子中map所需的闭包的返回值类型一致(其实是泛型),所以,可以省略return

第四步:

参数名称缩写(Shorthand Argument Names),由于Swift自动为内联闭包提供了参数缩写功能,你可以直接使用$0,$1,$2...依次获取闭包的第1,2,3...个参数。
如果您在闭包表达式中使用参数名称缩写,您可以在闭包参数列表中省略对其的定义,并且对应参数名称缩写的类型会通过函数类型进行推断。in关键字也同样可以被省略:

let results = values.map ({ $0 * 2 })
//"[2, 6, 10, 14]"

例子中的$0即代表闭包中的第一个参数。

最后一步:

尾随闭包,由于我们的闭包是作为最后一个参数传递给map函数的,所以我们可以将闭包表达式尾随:

let results = values.map (){ $0 * 2 }
//"[2, 6, 10, 14]"

如果函数只需要闭包表达式一个参数,当您使用尾随闭包时,您甚至可以把()省略掉:

let results = values.map { $0 * 2 }
//"[2, 6, 10, 14]"

如果还有不明白的,可以多翻阅翻阅The Swift Programming Language

FlatMap

与map一样,它可以用在 OptionalsSequenceType 上(如:数组、词典等)。我们先来看看针对Optional的定义:

Optionals进行flatMap操作
public enum Optional<Wrapped> : _Reflectable, NilLiteralConvertible {
    /// Returns `nil` if `self` is `nil`, `f(self!)` otherwise.
    @warn_unused_result
    public func flatMap<U>(@noescape f: (Wrapped) throws -> U?) rethrows -> U?
}

就闭包而言,这里有一个明显的不同,这次flatMap期望一个 (Wrapped) -> U?)闭包。对于可选值, flatMap 对于输入一个可选值时应用闭包返回一个可选值,之后这个结果会被压平,也就是返回一个解包后的结果。本质上,相比 map,flatMap也就是在可选值层做了一个解包。

var value:String? = "1"
var result = value.map { Int($0)}
/// "Optional(Optional(1))"
print(result)
var value:String? = "1"
var result = value.flatMap { Int($0)}
/// ""Optional(1)"
print(result)

使用flatMap就可以在链式调用时,不用做额外的解包工作:

var value:String? = "1"
var result = value.flatMap { Int($0)}.map { $0 * 2 }
/// ""Optional(2)"
print(result)
SequenceType进行flatMap操作

我们先来看看Swift中的定义

extension SequenceType {
    /// 返回一个将变换结果连接起来的数组
    /// `transform` over `self`.
    ///     s.flatMap(transform)
    /// is equivalent to
    ///     Array(s.map(transform).flatten())
    @warn_unused_result
    public func flatMap<S : SequenceType>(transform: (Self.Generator.Element) throws -> S) rethrows -> [S.Generator.Element]
}

extension SequenceType {
    /// 返回一个包含非空值的映射变换结果
    @warn_unused_result
    public func flatMap<T>(@noescape transform: (Self.Generator.Element) throws -> T?) rethrows -> [T]
}

通过这两个描述,就提现了flatMapSequenceType的两个作用:

一:压平
var values = [[1,3,5,7],[9]]
let flattenResult = values.flatMap{ $0 }
/// [1, 3, 5, 7, 9]
二:空值过滤
var values:[Int?] = [1,3,5,7,9,nil]
let flattenResult = values.flatMap{ $0 }
/// [1, 3, 5, 7, 9]

Filter

同样,我先来看看Swift中的定义:

extension SequenceType {
    /// 返回包含原数组中符合条件的元素的数组
    /// Returns an `Array` containing the elements of `self`,
    /// in order, that satisfy the predicate `includeElement`.
    @warn_unused_result
    public func filter(@noescape includeElement: (Self.Generator.Element) throws -> Bool) rethrows -> [Self.Generator.Element]
}

filter函数接受一个(Element) -> Bool)的闭包,来判断原数组中的元素是否符合条件,这个方法用来过滤数组中的一些元素再好不过了:

var values = [1,3,5,7,9]
let flattenResults = values.filter{ $0 % 3 == 0}
//[3, 9]

我们向flatMap传入了一个闭包,筛选出了能被3整除的数据。

Reduce

我们先来看下Swift中的定义:

extension SequenceType {
    /// Returns the result of repeatedly calling `combine` with an
    /// accumulated value initialized to `initial` and each element of
    /// `self`, in turn, i.e. return
    /// `combine(combine(...combine(combine(initial, self[0]),
    /// self[1]),...self[count-2]), self[count-1])`.
    @warn_unused_result
    public func reduce<T>(initial: T, @noescape combine: (T, Self.Generator.Element) throws -> T) rethrows -> T
}

给定一个初始化的combine结果,假设为result,从数组的第一个元素开始,不断地调用combine闭包,参数为:(result,数组中的元素),返回的结果值继续调用combine函数,直至元素最后一个元素,返回最终的result值。来看下面的代码(为了更方便你理解这个过程,代码就不简写了):

var values = [1,3,5]
let initialResult = 0
var reduceResult = values.reduce(initialResult, combine: { (tempResult, element) -> Int in
    return tempResult + element
})
print(reduceResult)
//9

我们存在一个数组[1,3,5],给定了一个初始化的结果 initialResult = 0,向reduce函数传了 (tempResult, element) -> Int的闭包,tempResut便是每次闭包返回的结果值,并且其初始值为我们之前设置的initialResult0element即为我们数组中的元素(可能为1,3,5)。reduce会一直调用combine闭包,直至数组最后一个元素。下面的代码更形象地描述了整个过程,这其实跟reduce所做的操作是等价的:

func combine(tempResult: Int, element: Int) -> Int  {
    return tempResult + element
}
reduceResult = combine(combine(combine(initialResult, element: 1), element: 3), element: 5)
print(reduceResult)
//9

以上所用的一些示例代码可以在我的Github中找到,如果您有什么建议可以在评论区留言。




参考链接:
1.http://www.jianshu.com/p/87b97dfbf17b

闭包 -> map / floatMap / filter / reduce 浅析

标签:

原文地址:http://www.cnblogs.com/Jenaral/p/5647288.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!