重温 Swift 闭包 `Closure`

一:闭包表达式

1:闭包类似匿名函数

1: 闭包类似匿名函数
2: 当一个函数的参数是闭包时,可以传入一个函数名,函数的参数和返回值类型要和闭包的参数和返回值类型相同

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
func backword(s1:String,s2:String) ->Bool
{
    return s1 > s2
}

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

2:闭包表达式

 { (parameters) -> returnType in
     <#statements#>
 }

3:标准闭包表达式

1: 标准的闭包表达式
2: 参数有圆括号括起来
3: 参数类型可类型推断,也可显式标注
4: 返回值类型要显示声明

let names3 = names.sorted(by: { (s1:String,s2:String) -> Bool in return s1 > s2})
print("3:\(names3)")

4:根据上下文推断类型

1: 参数的类型以及返回值的类型都由编译器推断
2: 省略参数的圆括号时,参数的类型一定不能显式类型标注,而要由编译器隐式推断,否则报编译时错误
3: 当返回值的类型可以由编译器推断出来时,返回值得类型可写可不写

let names4 = names.sorted(by: {s1,s2 -> Bool in return s1 > s2})
print("4:\(names4)")

5:单表达式闭包隐式返回

1: 闭包体中只有一行表达式
2: 表达式的返回类型可以推断出来
3: <#return#> 关键字可以省略

let names5 = names.sorted(by: {s1,s2 in  s1 > s2})
print("5:\(names5)")

6:参数名称缩写

1: 内联闭包可以提供了参数名缩写功能,直接可以通过 01 $2 来顺序调用闭包的参数
2: 如果在闭包表达式中使用了参数名称缩写,则可以在定义闭包时省略参数列表,不省略时会报错
3: 参数名称缩写的类型会通过函数类型进行推断
4: <#in#> 关键字也可以省略

let names6 = names.sorted(by: {$0 > $1})
print("6:\(names6)")

7:运算符方法

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

let names7 = names.sorted(by: >)
print("7:\(names7)")

二:尾随闭包

1:尾随闭包使用

1: 函数要将闭包参数写在参数列表的最后面才能使用尾随闭包
2: 尾随闭包是写在函数圆括号后面的闭包表达式

3: 使用尾随闭包时不用写出它的参数表示
4: 如果闭包表达式是函数或方法的唯一参数,则当你使用尾随闭包时,你甚至可以把 () 省略掉

func someFunctionThatTakesAClosure(closure: () -> Void) {
    // 函数体部分
}

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

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

2:(一:6)使用尾随闭包可以写成下面的样式

let names22 = names.sorted(){$0 > $1}
print("22:\(names22)")

三:值捕获

1:嵌套函数的值捕获

1: 嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。
2: 下面的函数返回值是 () -> Int 类型,返回值是一个函数而不是普通的值
3: 返回的函数不接收参数,每次调用返回一个Int数值
4: 单独考虑嵌套函数 incrementer(),会发现它有些不同寻常
5: incrementer() 函数并没有任何参数,但是在函数体内访问了 runningTotalamount 变量
6: 这是因为它从外围函数捕获了 runningTotalamount 变量的 引用
7: 捕获引用保证了 runningTotalamount 变量在调用完 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())

四:闭包是引用类型

1:闭包和函数都是引用类型

1: 函数和闭包都是引用类型。
2: 无论你将函数或闭包赋值给一个常量还是变量,你实际上都是将常量或变量的值设置为对应函数或闭包的引用

3: 上面的例子中,指向闭包的引用 inc 是一个常量,而并非闭包内容本身。
NB: 当闭包作为类的实例属性时,要注意循环引用(因为闭包是引用类型,类实例也是引用类型,两者相互引用会造成循环引用)

五:逃逸闭包

1: 当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸
2: 当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 @escaping,用来指明这个闭包是允许“逃逸”出这个函数的。
3: 一种能使闭包“逃逸”出函数的方法是,将这个闭包保存在一个函数外部定义的变量中
e.g: 举个例子,很多启动异步操作的函数接受一个闭包参数作为 completion handler。
这类函数会在异步操作开始之后立刻返回,但是闭包直到异步操作结束后才会被调用。
在这种情况下,闭包需要“逃逸”出函数,因为闭包需要在函数返回之后被调用。

1:逃逸闭包例子

1: someFunctionWithEscapingClosure(_:) 函数接受一个闭包作为参数,该闭包被添加到一个函数外定义的数组中
2: 如果你不将这个参数标记为 @escaping,就会得到一个编译错误。

var comletionHandlers:[()->Void] =  []
func someFunctionWithEscapingClosure(comletionHandler: @escaping ()->Void)
{
    comletionHandlers.append(comletionHandler)
}

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)

六:自动闭包

1: 自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。
2: 这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。
3: 这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。
4: 自动闭包让你能够延迟求值,因为直到你调用这个闭包,代码段才会被执行。
5: 延迟求值对于那些有副作用(Side Effect)和高计算成本的代码来说是很有益处的,因为它使得你能控制代码的执行时机。

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"

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!"

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"

七:逃逸自动闭包

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!"
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容