重温Swift闭包

2018-04-14  本文已影响4人  __Simon__
//
//  main.swift
//  Clousures
//
//  Created by Yubin on 2017/1/17.
//  Copyright © 2017年 X. All rights reserved.
//

import Foundation

print("Hello, World!")

// MARK:- 一:闭包表达式
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

// MARK:- 1:闭包类似匿名函数
/*---------------------------------------------------------------------------------
 | 1: 闭包类似匿名函数
 | 2: 当一个函数的参数是闭包时,可以传入一个函数名,函数的参数和返回值类型要和闭包的参数和返回值类型相同
 ---------------------------------------------------------------------------------*/
func backword(s1:String,s2:String) ->Bool
{
    return s1 > s2
}

let names2 = names.sorted(by: backword)
print("2:\(names2)")

// MARK:2:闭包表达式
/*---------------------------------------------------------------------------------
 | { (parameters) -> returnType in
 |
 |    <#statements#>
 | }
 --------------------------------------------------------------------------------*/

// MARK:3:标准闭包表达式
/*---------------------------------------------------------------------------------
 | 1: 标准的闭包表达式
 | 2: 参数有圆括号括起来
 | 3: 参数类型可类型推断,也可显式标注
 | 4: 返回值类型要显示声明
 ---------------------------------------------------------------------------------*/
let names3 = names.sorted(by: { (s1:String,s2:String) -> Bool in return s1 > s2})
print("3:\(names3)")

// MARK:4:根据上下文推断类型 
/*---------------------------------------------------------------------------------
 | 1: 参数的类型以及返回值的类型都由编译器推断
 | 2: 省略参数的圆括号时,参数的类型一定不能显式类型标注,而要由编译器隐式推断,否则报编译时错误
 | 3: 当返回值的类型可以由编译器推断出来时,返回值得类型可写可不写
 ---------------------------------------------------------------------------------*/
let names4 = names.sorted(by: {s1,s2 -> Bool in return s1 > s2})
print("4:\(names4)")

// MARK:5:单表达式闭包隐式返回
/*---------------------------------------------------------------------------------
 | 1: 闭包体中只有一行表达式
 | 2: 表达式的返回类型可以推断出来
 | 3: <#return#> 关键字可以省略
 ---------------------------------------------------------------------------------*/
let names5 = names.sorted(by: {s1,s2 in  s1 > s2})
print("5:\(names5)")

// MARK:6:参数名称缩写
/*---------------------------------------------------------------------------------
 | 1: 内联闭包可以提供了参数名缩写功能,直接可以通过 $0 $1 $2 来顺序调用闭包的参数
 | 2: 如果在闭包表达式中使用了参数名称缩写,则可以在定义闭包时省略参数列表,不省略时会报错
 | 3: 参数名称缩写的类型会通过函数类型进行推断
 | 4: <#in#> 关键字也可以省略
 ---------------------------------------------------------------------------------*/
let names6 = names.sorted(by: {$0 > $1})
print("6:\(names6)")

// MARK:7:运算符方法
/*---------------------------------------------------------------------------------
 | 1: String 类型定义了关于大于号(>)的字符串实现 (> 相当于一个方法,用来比较两个字符创的大小)
 | 2: (>)作为一个函数接受两个 String 类型的参数并返回 Bool 类型的值.而这正好与 sorted(by:) 方法的参数需要的函数类型相符合
 | 3: 因此,可以简单地传递一个大于号,Swift 可以自动推断出你想使用大于号的字符串函数实现
 ---------------------------------------------------------------------------------*/
let names7 = names.sorted(by: >)
print("7:\(names7)")


// MARK:- 二:尾随闭包

// MARK:- 1:尾随闭包使用
/*---------------------------------------------------------------------------------
 | 1: 函数要将闭包参数写在参数列表的最后面才能使用尾随闭包
 | 2: 尾随闭包是写在函数圆括号后面的闭包表达式
 | 3: 使用尾随闭包时不用写出它的参数表示
 | 4: 如果闭包表达式是函数或方法的唯一参数,则当你使用尾随闭包时,你甚至可以把 () 省略掉
 ---------------------------------------------------------------------------------*/
func someFunctionThatTakesAClosure(closure: () -> Void) {
    // 函数体部分
}

// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure(closure: {
    // 闭包主体部分
})

// 以下是使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
    // 闭包主体部分
}

// MARK:2:(一:6)使用尾随闭包可以写成下面的样式
/*---------------------------------------------------------------------------------
 ---------------------------------------------------------------------------------*/
let names22 = names.sorted(){$0 > $1}
print("22:\(names22)")

// MARK: - 三:值捕获

// MARK:- 1:嵌套函数的值捕获
/*---------------------------------------------------------------------------------
 | 1: 嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。
 | 2: 下面的函数返回值是 () -> Int 类型,返回值是一个函数而不是普通的值
 | 3: 返回的函数不接收参数,每次调用返回一个Int数值
 | 4: 单独考虑嵌套函数 incrementer(),会发现它有些不同寻常
 | 5: <#incrementer()#> 函数并没有任何参数,但是在函数体内访问了 runningTotal 和 amount 变量
 | 6: 这是因为它从外围函数捕获了 <#runningTotal#> 和 <#amount#> 变量的 **引用**。
 | 7: 捕获引用保证了 <#runningTotal#> 和 <#amount#> 变量在调用完 <#makeIncrementer#> 后不会消失,并且保证了在下一次执行 `incrementer` 函数时,`runningTotal` 依旧存在。
 |
 ---------------------------------------------------------------------------------*/
func makeIncrementer(forAmount amount:Int) -> ()->Int
{
    var runningTotal = 0
    func incrementer() -> Int
    {
        runningTotal += amount
        return runningTotal
    }
    
    return incrementer
}

var inc = makeIncrementer(forAmount: 2)
print(inc())
print(inc())
print(inc())
print(inc())
print(inc())


// MARK:- 四:闭包是引用类型
// MARK:- 1:闭包和函数都是引用类型
/*---------------------------------------------------------------------------------
 | 1: 函数和闭包都是引用类型。
 | 2: 无论你将函数或闭包赋值给一个常量还是变量,你实际上都是将常量或变量的值设置为对应函数或闭包的引用
 | 3: 上面的例子中,指向闭包的引用 <#inc#> 是一个常量,而并非闭包内容本身。
 | NB: 当闭包作为类的实例属性时,要注意循环引用(因为闭包是引用类型,类实例也是引用类型,两者相互引用会造成循环引用)
 ---------------------------------------------------------------------------------*/

// MARK:- 五:逃逸闭包
/*---------------------------------------------------------------------------------
 | 1: 当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸
 | 2: 当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 @escaping,用来指明这个闭包是允许“逃逸”出这个函数的。
 | 3: 一种能使闭包“逃逸”出函数的方法是,将这个闭包保存在一个函数外部定义的变量中
 | e.g: 举个例子,很多启动异步操作的函数接受一个闭包参数作为 completion handler。
 |      这类函数会在异步操作开始之后立刻返回,但是闭包直到异步操作结束后才会被调用。
 |      在这种情况下,闭包需要“逃逸”出函数,因为闭包需要在函数返回之后被调用。
 ---------------------------------------------------------------------------------*/
// MARK:- 1:逃逸闭包例子
/*---------------------------------------------------------------------------------
 | 1: <#someFunctionWithEscapingClosure(_:)#> 函数接受一个闭包作为参数,该闭包被添加到一个函数外定义的数组中
 | 2: 如果你不将这个参数标记为 @escaping,就会得到一个编译错误。
 ---------------------------------------------------------------------------------*/
var comletionHandlers:[()->Void] =  []
func someFunctionWithEscapingClosure(comletionHandler: @escaping ()->Void)
{
    comletionHandlers.append(comletionHandler)
}


// MARK:- 2:逃逸闭包必须显示引用<#self#>
/*---------------------------------------------------------------------------------
 | 1: 将一个闭包标记为 <#@escaping#> 意味着你必须在闭包中显式地引用 <#self#>
 | 2: 比如说,在下面的代码中,传递到 <#someFunctionWithEscapingClosure(_:)#> 中的闭包是一个逃逸闭包,这意味着它需要显式地引用 <#self#>。
 | 3: 相对的,传递到 <#someFunctionWithNonescapingClosure(_:)#> 中的闭包是一个非逃逸闭包,这意味着它可以隐式引用 <#self#>。
 ---------------------------------------------------------------------------------*/
func someFunctionWithNonescapingClosure(closure: ()->Void)
{
    closure()
}

class SomeClass {
    var x = 10;
    func doSomething(){
        someFunctionWithEscapingClosure { self.x = 100 }
        someFunctionWithNonescapingClosure { x = 100 }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)

// MARK:- 六:自动闭包
/*---------------------------------------------------------------------------------
 |   1: 自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。
 | * 2: 这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。
 |   3: 这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。
 |   4: 自动闭包让你能够延迟求值,因为直到你调用这个闭包,代码段才会被执行。
 |   5: 延迟求值对于那些有副作用(<#Side Effect#>)和高计算成本的代码来说是很有益处的,因为它使得你能控制代码的执行时机。
 |
 ---------------------------------------------------------------------------------*/
// MARK: - 1:下面的代码展示了闭包如何延时求值。
/*---------------------------------------------------------------------------------
 | 1: 尽管在闭包的代码中,customersInLine 的第一个元素被移除了,不过在闭包被调用之前,这个元素是不会被移除的。
 | 2: 如果这个闭包永远不被调用,那么在闭包里面的表达式将永远不会执行,那意味着列表中的元素永远不会被移除。
 | NB: <#customerProvider#> 的类型不是 <#String#>,而是 <#() -> String#>,一个没有参数且返回值为 <#String#> 的函数。
 ---------------------------------------------------------------------------------*/
var customersInLine = ["Chris","Alex","Ewa","Barry","Daniella"]
print(customersInLine.count)
// 打印出 "5"

let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// 打印出"5"

print("Now serving \(customerProvider())!")
// 打印出"Now serving Chris!"
print(customersInLine.count)
// 打印出"4"

// MARK:2:闭包作为参数传递给函数时,能获得同样的延时求值行为
/*---------------------------------------------------------------------------------
 | 1: <#serve(customer:)#> 函数接受一个返回顾客名字的显式的闭包。
 |
 |
 ---------------------------------------------------------------------------------*/
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// 打印出 "Now serving Alex!"

// MARK:3:自动闭包作为函数的参数
/*---------------------------------------------------------------------------------
 | 1: 下面这个版本的 <#serve(customer:#>) 完成了相同的操作,不过它并没有接受一个显式的闭包,而是通过将参数标记为 @autoclosure 来接收一个自动闭包。
 | 2: 现在你可以将该函数当作接受 String 类型参数(而非闭包)的函数来调用。
 | 3: customerProvider 参数将自动转化为一个闭包,因为该参数被标记了 @autoclosure 特性。
 | NB: 过度使用 autoclosures 会让你的代码变得难以理解。上下文和函数名应该能够清晰地表明求值是被延迟执行的。
 ---------------------------------------------------------------------------------*/
// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure ()->String)
{
    print("Now serving \(customerProvider())")
}
serve(customer: customersInLine.remove(at: 0))
// 打印出"Now serving Ewa"

// MARK:- 七:逃逸自动闭包
/*---------------------------------------------------------------------------------
 | 1: 如果你想让一个自动闭包可以“逃逸”,则应该同时使用 @autoclosure 和 @escaping 属性。
 | 2: 在下面的代码中,collectCustomerProviders(_:) 函数并没有调用传入的 customerProvider 闭包,而是将闭包追加到了 customerProviders 数组中。
 | 3: 这个数组定义在函数作用域范围外,这意味着数组内的闭包能够在函数返回之后被调用
 | 4: 因此,customerProvider 参数必须允许“逃逸”出函数作用域。
 ---------------------------------------------------------------------------------*/
// customersInLine i= ["Barry", "Daniella"]
var customerProviders:[()->String] = []
func collectCustomerProviders(_ customerProvider:@autoclosure @escaping ()->String)
{
    customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))

print("Collect \(customerProviders.count) closures.");
// 打印"Collect 2 closures."
for customerProvider in customerProviders
{
    print("Now serving \(customerProvider())!")
}
// 打印 "Now serving Barry!"
// 打印 "Now serving Daniella!"
上一篇 下一篇

猜你喜欢

热点阅读