一 闭包
闭包是具有一定功能的代码块,其实是函数的简写形式。
在swift中函数也是变量,也可以作为其他函数的参数。可以让我们不用费事去定义个函数,而是直接写在使用处,类似js中的箭头函数.
• 全局函数是一个有名字但不会捕获任何值的闭包
• 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
• 闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包
举个🌰
swift为数组定义了这样一个排序函数:
public func sorted(by areInIncreasingOrder: (Element, Element) -> Bool) -> [Element]
从定义可以看出来这个函数接收一个函数类型的参数(Element,Element) -> Bool 然后返回排序结果数组。
其中这个函数参数的功能是完成两个元素的比价。可以向下面这样实现:
let nums = [2,4,90,-5,125];
let res = nums.sorted(by: compare);
func compare(num1:Int,num2:Int) -> Bool {
return num1 > num2;
}
用闭包来实现是这样的:
let nums = [2,4,90,-5,125];
//这样就不需要定义一个函数了,简洁了许多
let resArr = nums.sorted(by: { (s1:Int,s2:Int) -> Bool in
return s1 > s2;
})
闭包表达式语法
{ (parameters) -> 返回类型 in
statements
}
因为swift可以根据闭包内的表达式来推断出参数的类型和返回值的类型(比较神奇),所以这些都可以省略:
let nums = [2,4,90,-5,125];
//注意这个in不能省
let resArr = nums.sorted(by: { s1,s2 in
return s1 > s2;
})
另外当表达式只有一个的时候连return都可以省略
let resArr = nums.sorted(by: { s1,s2 in s1 > s2})
还可以省略:对于单行表达式swift可以为其提供简略参数名.这时候参数就不需要了,in也可以省略
let nums = [2,4,90,-5,125];
let resArr = nums.sorted(by: { $0 > $1});
完了吗?并没有。还可以简化:因为运算符本身就是是函数,也就是说运算符是代表了一种类型的函数,而(Element, Element) -> Bool 这种函数类型刚好和逻辑运算符 >或<一样。都是接受两个参Int参数返回一个Bool值。so 还可以简化城下面这样:
let nums = [2,4,90,-5,125];
let resArr = nums.sorted(by: >);
简直炫酷!
后置闭包
let nums = [2,4,90,-5,125];
let resArr = nums.sorted(by: { $0 > $1});
//调用sorted函数也可以写成这样
let resArr = nums.sorted(){ $0 > $1};
//当函数只有一个闭包参数的时候,调用函数的小括号也可以省略。
let resArr = nums.sorted{ $0 > $1};
当一个函数的最后一个参数为闭包(函数)时,可以吧这个闭包拿到括号外面来,这应该是为了增加代码可读性。避免出现括号包含过多的情况.
注意:这只是一种函数调用时候的写法
值捕获
闭包可以捕获其上下文中的常量和变量,捕获不是仅仅是在闭包中可以使用这些变量,而是即使原本定义这些变量的作用域已经不存在了的情况下闭包还能使用。
如:内嵌函数其实是一种有名字且只能捕获其父函数的值域内值的闭包
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let increment1 = makeIncrementer(forIncrement: 10);
let increment2 = makeIncrementer(forIncrement: 7);
print(increment1())
print(increment1())
print(increment2())
print(increment2())
//10
//20
//7
//14
上面代码中incrementer是个内部函数,它捕获了父函数的参数 amount和变量runningTotal,使increment1和increment2两个返回的函数都可以使用这两个值,并且互不影响。
相当于在内嵌函数里面又重新定义了这两个值。
注意: 闭包和函数都是引用类型,就是在赋值的时候传的是引用,而并没有拷贝。
逃逸闭包
闭包作为函数参数时候,默认情况下 这个闭包只能在函数内部使用,不能作为返回值,也不能赋值给其他变量或常量。使用@escaping可以让闭包脱离函数的限制。
//向下面这样把闭包存在一个数组里面就要声明为escaping
var closuresArr:[()->Void] = [];
func someFunctionWithEscapingClosure(completionHandler: @escaping ()->Void){
closuresArr.append(completionHandler);
}
有个规定:逃逸闭包中如果要使用类的其他成员,要显示的使用self
class SomeClass {
var x = 10
func doSomething() {
//逃逸闭包
someFunctionWithEscapingClosure { self.x = 100 }
//普通闭包
someFunctionWithNonescapingClosure { x = 200 }
}
}
自动闭包
上代码说明:
var customersInLine = ["Alex", "Ewa", "Barry", "Daniella"];
//有一个函数 参数为 ()->String 类型
func serve(customerProvider:()->String) {
print("Now serving \(customerProvider())");
}
//可以这样调用
serve(customerProvider: {()->String in
return customersInLine.remove(at: 0);
});
//简化一下
serve(customerProvider: {customersInLine.remove(at: 0)});
//此时如果在函数声明时候对参数使用@autoclosure
func serve(customerProvider:@autoclosure ()->String) {
print("Now serving \(customerProvider())");
}
//那么在调用的时候可以连大括号都省略
serve(customerProvider: customersInLine.remove(at: 0));
//虽然这么写比较简便 但是用多了会影响代码的可读性
//当自动闭包想要逃脱的时候可以两个一起使用@autoclosure @escaping
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
customerProviders.append(customerProvider)
}
可以向类似block这样去声明和使用一个闭包
注意当闭包调用之前count一直是5
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// Prints "5"
//这里仅仅是声明
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// Prints "5"
//这里调用闭包
customerProvider()
print(customersInLine.count)
// Prints "4"
网友评论