Swift 闭包(一)
函数介绍
在我们分析闭包之前,先来分析一下函数。
函数类型
之前在代码的书写过程中,我们已经或多或少的接触过函数,函数本身也有自己的类型,它由形
式参数类型,返回类型组成。下面我们看几个案例。
- 案例 1
func addTwoInts(_ a: Int, _ b: Int) -> Int {
return a + b
}
var a = addTwoInts
a(10, 20)
这里定义了一个 addTwoInts
函数, 将 addTwoInts
赋值给 a
这个变量,然后就可以通过 a
这个变量来执行这个函数,这里 addTwoInts
代表的就是这个函数的类型。
- 案例 2
// 函数 1
func addTwoInts(_ a: Int, _ b: Int) -> Int {
return a + b
}
// 函数 2
func addTwoInts(_ a: Double, _ b: Double) -> Double {
return a + b
}
var a: (Double, Double) -> Double = addTwoInts
a(10.0, 20.0)
var b = a
b(20.0, 30.0)
在 swift
中如果出现同名函数的时候,如果要讲函数赋值给一个变量,需要指明变量的函数类型((Double, Double) -> Double
),这时候 a
的值就是函数 2,分别执行 a(10.0, 20.0)
或者 b(20.0, 30.0)
代码的时候其实就是在调用函数 2 。 这里需要明确的一点是函数也是引用类型。
函数的 Metadata
对于函数来说,也有对应的 Metadata
,下面我们在源码中来看一下。
struct TargetFunctionTypeMetadata : public TargetMetadata<Runtime> {
using StoredSize = typename Runtime::StoredSize;
using Parameter = ConstTargetMetadataPointer<Runtime, swift::TargetMetadata>;
TargetFunctionTypeFlags<StoredSize> Flags;
/// 返回值类型
ConstTargetMetadataPointer<Runtime, swift::TargetMetadata> ResultType;
/// 参数类型,这里其实是一个连续的内存空间
Parameter *getParameters() { return reinterpret_cast<Parameter *>(this + 1); }
const Parameter *getParameters() const {
return reinterpret_cast<const Parameter *>(this + 1);
}
Parameter getParameter(unsigned index) const {
assert(index < getNumParameters());
/// 这里读取的时候是通过 index 获取
return getParameters()[index];
}
}
class TargetFunctionTypeFlags {
enum : int_type {
// 参数掩码
NumParametersMask = 0x0000FFFFU,
ConventionMask = 0x00FF0000U,
ConventionShift = 16U,
ThrowsMask = 0x01000000U,
ParamFlagsMask = 0x02000000U,
EscapingMask = 0x04000000U,
DifferentiableMask = 0x08000000U,
GlobalActorMask = 0x10000000U,
AsyncMask = 0x20000000U,
// 判断是否是逃逸类型
SendableMask = 0x40000000U,
};
在以上源码中我们可以看到 TargetFunctionTypeMetadata
继承于 TargetMetadata
,所以包含 kind
属性,除此之外 Flags
中可以看到参数掩码、SendableMask
(是逃逸类型)等参数,ResultType
代表返回值类型,根据源码中的这些信息我们可以还原如下代码:
func addTwoInts(_ a: Double, _ b: Double) -> Double {
return a + b
}
struct TargetFunctionTypeMetadata{
var kind: Int
var flags: Int
var arguments: ArgumentsBuffer<Any.Type>
func numberArguments() -> Int{
return self.flags & 0x0000FFFF
}
}
struct ArgumentsBuffer<Element>{
var element: Element
mutating func buffer(n: Int) -> UnsafeBufferPointer<Element> {
return withUnsafePointer(to: &self) {
let ptr = $0.withMemoryRebound(to: Element.self, capacity: 1) { start in
return start
}
return UnsafeBufferPointer(start: ptr, count: n)
}
}
mutating func index(of i: Int) -> UnsafeMutablePointer<Element> {
return withUnsafePointer(to: &self) {
return UnsafeMutablePointer(mutating: UnsafeRawPointer($0).assumingMemoryBound(to: Element.self).advanced(by: i))
}
}
}
let value = type(of: addTwoInts)
let functionType = unsafeBitCast(value as Any.Type, to: UnsafeMutablePointer<TargetFunctionTypeMetadata>.self)
//输出参数的个数
print(functionType.pointee.numberArguments())
闭包
什么是闭包
闭包是一个捕获了上下文的常量或者是变量的函数。
func makeIncrementer() -> () -> Int {
var runningTotal = 10
func incrementer() -> Int {
runningTotal += 1
return runningTotal
}
return incrementer
}
var a = makeIncrementer
这里可以看到,内部函数 incrementer
的生命周期明显比外部函数 makeIncrementer
的生命周期长,当执行 return incrementer
外部函数的生命周期就已经结束了,而内部函数被返回出去,并赋值给了变量 a
,所以内部函数可以延时执行,而内部函数中用到了外部函数的变量 runningTotal
,所以要对 runningTotal
进行捕获,这里 incrementer
就是闭包。
- 闭包表达式
{ (param) -> (returnType) in
//do something
}
按照我们之前的知识积累,OC
中的 Block
其实是一个匿名函数,所以这个表达式要具备以下几个条件
- 作用域(也就是大括号)
- 参数和返回值
- 函数体(in)之后的代码
Swift
中的闭包即可以当做变量,也可以当做参数传递,这里我们来看一下下面的例子熟悉一 下:
var closure : (Int) -> Int = { (age: Int) in
return age
}
同样的我们也可以把我们的闭包声明一个可选类型:
//错误的写法
var closure : (Int) -> Int? closure = nil
//正确的写法
var closure : ((Int) -> Int)? closure = nil
还可以通过 let 关键字将闭包声明位一个常量(也就意味着一旦赋值之后就不能改变了)
let closure: (Int) -> Int
closure = {(age: Int) in
return age
}
closure = {(age: Int) in
return age
}
同时也可以作为函数的参数
func test(param : () -> Int){
print(param())
}
var age = 10
test { () -> Int in
age += 1
return age
}
尾随闭包
当我们把闭包表达式作为函数的最后一个参数,如果当前的闭包表达式很⻓,我们可以通过尾随
闭包的书写方式来提高代码的可读性。
func test(_ a: Int, _ b: Int, _ c: Int, by: (_ item1: Int, _ item2: Int, _ item3: Int) -> Bool) -> Bool {
return by(a, b, c)
}
test(10, 20, 30, by: {(_ item1: Int, _ item2: Int, _ item3: Int) -> Bool in
return (item1 + item2 < item3)
})
以上是一个正常的闭包,通过尾随闭包的方式书写如下:
func test(_ a: Int, _ b: Int, _ c: Int, by: (_ item1: Int, _ item2: Int, _ item3: Int) -> Bool) -> Bool {
return by(a, b, c)
}
test(10, 20, 30) {item1,item2,item3 in
return (item1 + item2 < item3)
}
通过对比可以看到,以闭包的形式书写,代码会简洁很多。
闭包表达式是 Swift
语法。使用闭包表达式能更简洁的传达信息。当然闭包表达式的好处有很多:
- 利用上下文推断参数和返回值类型
- 单表达式可以隐士返回,既省略
return
关键字 - 参数名称的简写(比如我们的
$0
) - 尾随闭包表达式
var array = [1, 2, 3]
array.sort(by:{(item1:Int, item2:Int)-> Bool in return item1 < item2 })
array.sort(by:{(item1,item2)-> Bool in return item1 < item2 })
array.sort(by:{(item1,item2)in return item1 < item2 })
array.sort{(item1,item2)in item1 < item2}
array.sort{ return $0 < $1 }
array.sort{ $0 < $1 }
array.sort(by:<)
虽然代码可以像上面一步步的进行省略简写,但是我们在书写代码的时候也要注意代码的可读性,省略的太多的话也会造成代码可读性变差。
捕获值
在讲闭包捕获值的时候,我们先来回顾一下 Block
捕获值的情形
- (void)testBlock {
NSInteger i = 1;
void(^block)(void) = ^{
NSLog(@"block %ld:", i);
};
i += 1;
NSLog(@"before block %ld:", i);
block();
NSLog(@"after block %ld:", i);
}
这里当我们对 i
的值进行加 1之后,再执行 block
,可以看到 block
内部 i
的值并没有变化,那么如果我们想要外部的修改能够影响当前 block
内部捕获的值,我们只需要对当前的 i
添加 __block
修饰符就行。
- (void)testBlock {
__block NSInteger i = 1;
void(^block)(void) = ^{
NSLog(@"block %ld:", i);
};
i += 1;
NSLog(@"before block %ld:", i);
block();
NSLog(@"after block %ld:", i);
}
那么我将以上代码用 swift
来书写,看一下:
这里可以看到,用 swift
来写的话闭包中 i
的值是修改过的,那么我们将 swift
转成 SIL
代码来看一下。
在 SIL
代码中可以看到,闭包执行的时候是直接获取全局变量 i
的地址,所以在外部修改 i
的值,在闭包中打印 i
的值也是修改过的。
下面我们将以上代码放到函数中再转成 SIL
代码来看一下。
func test() {
var i = 1
let closure = {
print("closure:\(i)")
}
i += 1
print("before closure:\(i)")
closure()
print("after closure:\(i)")
}
test();
通过 SIL
代码可以看到,将闭包执行放到 test
函数中之后,变量 i
会被捕获到堆区。
在 OC
中 block
分为全局 block
、堆区 block
、栈区 block
这三种情况,但是在 Swift
中闭包并没有这些概念,闭包本质上来讲也是一个函数。