(学习计划:从官方教程中学习最官方的使用方法------闭包篇)
-------------------------------------------------
闭包的创建和使用
-------------------------------------------------
创建闭包的常用格式:
typealias NamesClosures = (_ s1: String, _ s2: String) -> String
var namesClosures: NamesClosures?
或者
var namesClosures: ((_ s1: String, _ s2: String) -> (String))?
使用直接调用即可
//闭包的主体由in关键字引入。这个关键字表示闭包的参数和返回类型的定义已经完成,闭包的主体即将开始
namesClosures = { (s1: String, s2: String) -> (String) in
return s1 + " and " + s2
}
let name = namesClosures1?("Tom", "Jack")
//"name = Tom and Jack"
接下来让我们一步一步简化它
//因为Swift可以推断出它的参数类型以及它返回值的类型。所以可以省略返回箭头(->)和参数名称周围的括号(String: String)
namesClosures = { s1, s2 in return s1 + " and " + s2 }
让我们再进一步简化
//单表达式闭包可以隐式返回单个表达式的结果,所以我们可以省略关键字return
namesClosures = { s1, s2 in s1 + " and " + s2 }
我们依然可以进行更swift的简化
//由于swift自动为内联闭包提供速记参数名,参数名依照参数顺序指的是$0,$1,$2...
//如果我们想省略闭包的参数列表,那么必须要在主体中实现所有参数值的简写
//同时也可以省略in关键字,因为闭包表达式完全由其主体组成
//在这里,$0和$1依次等于闭包中的String参数s1和s2
namesClosures = { $0 + " and " + $1 }
-------------------------------------------------
Capturing Values(捕获值)
-------------------------------------------------
闭包可以从定义它的上下文中捕获常量和变量。然后闭包可以引用并修改其常量和变量的值。
//这是一个调用函数的示例makeIncrementer,包含一个嵌套函数incrementer,并捕获两个值allAmount和value。
//捕获这些值后,makeIncrementer将incrementer作为一个可以返回allAmount值的闭包返回,amount每次调用时它都会递增。
fileprivate func summation(forIncrement value: Int) -> () -> Int {
var allAmount = 0
func incrementer() -> Int {
allAmount += value
return allAmount
}
return incrementer
}
该incrementer()函数没有任何参数,但它通过Capturing捕获在其函数体内引用runningTotal和amount。通过捕捉保证runningTotal和amount不消失的时候调用直到summation函数结束,而且也保证了runningTotal在下一次incrementer函数中被调用。
-下方是官方文档中的注意事项,我这一块还不太明白(意思是说swift会自动对闭包中捕获值进行内存管理还是说需要手动管理?)
NOTE
As an optimization, Swift may instead capture and store a copy of a value if that value is not mutated by a closure, and if the value is not mutated after the closure is created.
Swift also handles all memory management involved in disposing of variables when they are no longer needed.
使用示例
//是一个let常量 () -> Int
let incrementByTen = summation(forIncrement: 10)
//此时只是创建,其内部的allAmount = 0
incrementByTen()
//allAmount = 10
incrementByTen()
//allAmount = 20
//额外的
let copyIncrementByTen = incrementByTen
copyIncrementByTen()
//allAmount = 30
incrementByTen()
//allAmount = 40
//可以看出这是复制一个新的对象,但是指针依然都指向同一个闭包地址,并都会递增返回总计运算
-------------------------------------------------
Escaping Closures(逃逸闭包)
-------------------------------------------------
逃逸闭包是说当参数传递给函数,但却是函数返回之后被调用的函数。我们通过在参数之前写入@escaping,声明这个闭包允许转义。(如果不写@escaping,但是却符合逃逸闭包的特性,xcode会提醒式报错)
闭包可以转义的一种方法是存贮在函数外部定义的变量中比如数组。许多异步操作的函数会把闭包在完成后调用处理,也就是说在函数操作完成之前不会调用闭包,闭包需要转义,以便稍后调用。
var escapingClosures: [ () -> Void ] = []
fileprivate func getEscapingClosure(_ escapingClosures: @escaping () -> Void) {
escapingClosures.append(escapingClosures)
}
一个简单的使用例子
class ViewModel: NSObject {
var escapingClosures : [ () -> Void ] = []
var url = ""
fileprivate func getEscapingClosure(_ escapingClosures: @escaping () -> Void) {
escapingClosures.append(escapingClosures)
}
fileprivate func getNoEscapingClosure(_ closures: () -> Void) {
closures()
}
func longTimeBegin() {
getNoEscapingClosure( { url = "www.baidu.com" } )
getEscapingClosure( { self.url = "www.google.com" } )
}
}
{
let viewModel = ViewModel()
viewModel.longTimeBegin()
//print(viewModel.url) = www.baidu.com
viewModel.escapingClosures.first?()
//print(viewModel.url) = www.google.com
}
-------------------------------------------------
Autoclosures(自动闭包)
-------------------------------------------------
autoclosure是自动创建闭包来包装作为参数传递给函数的表达式。我们不能给他的函数确定需要传入的值
例如:(@autoclosure (s1: String) -> String)
当它被调用时,它返回包含在其中的表达式的值。这种语法方便使您可以通过编写普通表达式而不是显式闭包来省略函数参数周围的大括号。
以下是官方给出的注意事项
NOTE
Overusing autoclosures can make your code hard to understand. The context and function name should make >it clear that evaluation is being deferred.
让我们看1️⃣下它可以怎么使用
//nums = [1,2,3,4,5]
fileprivate func removeNums(_ numClosures: () -> Int) {
print("num is \(numClosures())")
}
removeNums({ nums.remove(at: 0) })
//print -> "num is 1"
使用 @autoclosure
//[2,3,4,5]
fileprivate func removeNums(_ numClosure: @autoclosure () -> Int) {
print("num is \(numClosure())")
}
removeNums( nums.remove(at: 0) )
//print -> "num is 2"
我们可以结合@escaping一起使用
//nums = [3,4,5]
var escapingClosures : [ () -> Void ] = []
fileprivate func getEscapingClosure(_ escapingClosures: @autoclosure @escaping () -> Int) {
escapingClosures.append(escapingClosures)
}
getEscapingClosure( nums.remove(at: 0) )
getEscapingClosure( nums.remove(at: 0) )
getEscapingClosure( nums.remove(at: 0) )
for closure in escapingClosures {
print("count = \(nums.count), num is \(closure()), afterReCount = \(nums.count)")
}
//print -> count = 3, num is 3, afterReCount = 2
//print -> count = 2, num is 4, afterReCount = 1
//print -> count = 1, num is 5, afterReCount = 0