一、函数类型
1.1 示例
函数本身也有自己的类型,它由形 式参数类型,返回类型组成。
func addTwoInts(_ a: Int, _ b: Int) -> Int{
return a + b
}
var m = addTwoInts
print(m(10,20))
打印输出:
30
如果有两同名不同参数类型的函数,必须指明参数类型
正确如下:
func addTwoInts(_ a: Int, _ b: Int) -> Int{
return a + b
}
func addTwoInts(_ a: Double, _ b: Double) -> Double{
return a + b
}
var m: (Double, Double) -> Double = addTwoInts
print(m(10,20))
打印输出:
30.0
1.2 lldb调试查看函数数型
(lldb) po m
(Function)
(lldb) po withUnsafePointer(to: &m){print($0)}
0x00007ffeefbff2c0
0 elements
(lldb) x/8g 0x00007ffeefbff2c0
0x7ffeefbff2c0: 0x00007fff816ded38 0x00000001004fed80
0x7ffeefbff2d0: 0x0000000000000000 0x0000000000000000
0x7ffeefbff2e0: 0x0000000000000000 0x0000000000000000
0x7ffeefbff2f0: 0x00007ffeefbff3e0 0x0000000100002ab0
(lldb) cat address 0x00007fff816ded38
address:0x00007fff816ded38, 13420type metadata for () <+8> , ($sytN), External: NO libswiftCore.dylib.__DATA_CONST.__const +13420
所以说,函数在swift中也是一种引用类型,m的赋值是把函数的metadata赋值给了m
同样声明var n = m,打印输出两个内存地址相同
func addTwoInts(_ a: Double, _ b: Double) -> Double{
return a + b
}
var m: (Double, Double) -> Double = addTwoInts
var n = m
(lldb) po withUnsafePointer(to: &m){print($0)}
0x00007ffeefbff290
0 elements
(lldb) po withUnsafePointer(to: &n){print($0)}
0x00007ffeefbff290
0 elements
1.3 源码查看
在Metadata.h文件中,搜索FunctionTypeMetadata,发现FunctionTypeMetadata继承自TargetFunctionTypeMetadata,这个是继承自TargetMetadata,TargetMetadata可类比于我们常说的isa
using FunctionTypeMetadata = TargetFunctionTypeMetadata<InProcess>;
struct TargetFunctionTypeMetadata : public TargetMetadata<Runtime>{
...
TargetFunctionTypeFlags<StoredSize> Flags;
/// The type metadata for the result type.
ConstTargetMetadataPointer<Runtime, swift::TargetMetadata> ResultType;
...
}
TargetMetadata中有一个属性Flags,跟踪进去
class TargetFunctionTypeFlags {
// If we were ever to run out of space for function flags (8 bits)
// one of the flag bits could be used to identify that the rest of
// the flags is going to be stored somewhere else in the metadata.
enum : int_type {
//参数
NumParametersMask = 0x0000FFFFU,
ConventionMask = 0x00FF0000U,
ConventionShift = 16U,
//Throw关键字
ThrowsMask = 0x01000000U,
ParamFlagsMask = 0x02000000U,
//是否Escaping
EscapingMask = 0x04000000U,
DifferentiableMask = 0x08000000U,
GlobalActorMask = 0x10000000U,
AsyncMask = 0x20000000U,
SendableMask = 0x40000000U,
// NOTE: The next bit will need to introduce a separate flags word.
};
二、闭包
2.1 什么是闭包
闭包是一个捕获了上下文的常量或者变量的函数
{(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 }
同时也可以作为函数的参数
func test(param : () -> Int){
print(param())
}
var age = 10
test { () -> Int in
age += 1
return age1
}
2.2 尾随闭包
当我们把闭包表达式作为函数的最后一个参数,如果当前的闭包表达式很长,我们可以通过尾随 闭包的书写方式来提高代码的可读性。
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: <)
-(void)testBlock{
__blockNSIntegeri=1;
void(^block)(void)=^{
NSLog(@"block%ld:",i);
};
i+=1;
NSLog(@"beforeblock%ld:",i);
block();
NSLog(@"afterblock%ld:",i);
}
打印结果:
2
2
2
三、捕获值
3.1 OC捕获
在讲闭包捕获值的时候,我们先来回顾一 下 Block 捕获值的情形
-(void)testBlock{
NSIntegeri=1;
void(^block)(void)=^{
NSLog(@"block%ld:",i);
};
i+=1;
NSLog(@"beforeblock%ld:",i);
block();
NSLog(@"afterblock%ld:",i);
}
打印结果:
2
1
2
那么如果我们想要外部的修改能够影响当前block内部捕获的值,我们只需要对当前的i添加__block修饰符
-(void)testBlock{
__blockNSIntegeri=1;
void(^block)(void)=^{
NSLog(@"block%ld:",i);
};
i+=1;
NSLog(@"beforeblock%ld:",i);
block();
NSLog(@"afterblock%ld:",i);
}
打印结果:
2
2
2
3.2 Swift捕获
var i = 1
let closure = {
print("closure:\(i)")
}
i += 1
print("before closure:\(i)")
closure()
print("after closure:\(i)")
打印输出:
2
1
2
Swift其实不是捕获,是在全局变量里获取
查看上面例子的sil文件
// closure #1 in
sil private @$s4mainyycfU_ : $@convention(thin) () -> () {
bb0:
%0 = global_addr @$s4main1iSivp : $*Int // user: %27
%1 = integer_literal $Builtin.Word, 1 // user: %3
// function_ref _allocateUninitializedArray<A>(_:)
%2 = function_ref @$ss27_allocateUninitializedArrayySayxG_BptBwlF : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // user: %3
%3 = apply %2<Any>(%1) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // users: %5, %4
上面swift代码中是从全局变量中获取i的地址,编译器会在编译时将i的值放入全局变量
将上面示例放入方法中
func test(){
var i = 1
let closure = {
print("closure:\(i)")
}
i += 1
print("before closure:\(i)")
closure()
print("after closure:\(i)")
}
test()
查看sil:
// specialized closure #1 in test()
sil private @$s4main4testyyFyycfU_Tf0s_n : $@convention(thin) (@inout_aliasable Int) -> () {
// %0 "i" // users: %28, %1
bb0(%0 : $*Int):
debug_value_addr %0 : $*Int, var, name "i", argno 1 // id: %1
%2 = integer_literal $Builtin.Word, 1 // user: %4
// function_ref _allocateUninitializedArray<A>(_:)
%3 = function_ref @$ss27_allocateUninitializedArrayySayxG_BptBwlF : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // user: %4
%4 = apply %3<Any>(%2) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // users: %6, %5
可以看到,在方法中i变成了堆区变量
3.3 全局变量和局部变量捕获的区别
-
全局变量默认不捕获
-
局部变量本身引用地址就在堆区,直接调用堆区地址
-
多个变量捕获是将多个变量的地址传递
3.4 block和闭包的相互调用
3.4.1 Swift调OC的block
OC代码如下:
//h文件
typedef void(^ResultBlock)(NSError *error);
@interface LGTest : NSObject
+ (void)testBlockCall:(ResultBlock)block;
@end
//m文件
@implementation LGTest
+ (void)testBlockCall:(ResultBlock)block{
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:400 userInfo:nil];
block(error);
}
@end
查看.h文件转换成的swift代码
swift代码中直接调用
LGTest.testBlockCall{error in
let errorcast = error as NSError
print(errorcast)
}
打印输出:
Error Domain=NSURLErrorDomain Code=400 "(null)"
3.4.2 OC调Swift闭包
swift代码
class LGTeacher: NSObject{
@objc static var closure: (() -> ())?
}
查看swift文件对应的.h文件
编译过的.swift文件对应的.h文件如下
SWIFT_CLASS("_TtC11LGSwiftTest9LGTeacher")
@interface LGTeacher : NSObject
SWIFT_CLASS_PROPERTY(@property (nonatomic, class, copy) void (^ _Nullable closure)(void);)
+ (void (^ _Nullable)(void))closure SWIFT_WARN_UNUSED_RESULT;
+ (void)setClosure:(void (^ _Nullable)(void))value;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
OC中调用:
+(void)test{
// LGTeacher.test{}
LGTeacher.closure=^{
NSLog(@"end");
};
// LGTeacher.closure=^{//
// }
}
LGTest.test()
四、逃逸闭包和非逃逸闭包
4.1 逃逸闭包
逃逸闭包的定义:当闭包作为一个实际参数传递给一个函数的时候,并且是在函数返回之后调用,我们就说这个闭包逃逸了。当我们声明一个接受闭包作为形式参数的函数时,你可以在形式 参数前写 @escaping 来明确闭包是允许逃逸的。
- 作为函数的参数传递
- 当前闭包在函数内部异步执行或者被存储
- 函数结束,闭包被调用,生命周期结束
注意:可选类型默认是逃逸闭包!!!
class Teacher{
//定义一个闭包
var completionHandle: ((Int) -> Void)?
func makeIncrementer(_ amout: Int, handle: @escaping (Int) -> Void){
var runningTool = 10
runningTool += amout
// self.completionHandle = handle
DispatchQueue.global().asyncAfter(deadline: .now()) {
handle(runningTool)
}
}
func dosomething(){
self.makeIncrementer(10){
print($0)
}
}
}
4.2 非逃逸闭包
非逃逸闭包外部变量不会捕获到堆上,非逃逸闭包生命周期是确定的,闭包调用完就释放
非逃逸闭包好处:
- 不会产生循环引用,函数作用域内释放
- 编译器更多性能优化 (retain, relsase)
- 上下文的内存保存在栈上,不是堆上
五、自动闭包
是一种用来把实际参数传递给函数表达式打包的闭包,不接受任何实际参数,当其调用是,返回 内部表达式的值。 好处:用普通表达式代替闭包的写法,语法糖的一种
func debugOutPrint(_ condition: Bool, _ message: @autoclosure () -> String) {
if condition{
print("debug:\(message())")
}
}
func dosomthing() -> String{
return "Error Occured"
}
//方法调用者可以传一个String,也可以传一个closure
debugOutPrint(true, dosomthing())
debugOutPrint(true, dosomthing)
六、defer关键字
6.1 定义:
defer {} 里的代码会在当前代码块返回的时候执行,无论当前代码块是从哪个分支 return 的,即使程序抛出错误,也会执行。 如果多个 defer 语句出现在同一作用域中,则它们出现的顺序与它们执行的顺序相反,也就是 先出现的后执行。 示例如下:
func f(){
defer{print("Firstdefer")}
defer{print("Seconddefer")}
print("Endoffunction")
}
f()
打印输出:
Endoffunction
Seconddefer
Firstdefer
var a = 1
func add() -> Int{
defer{
a = a + 1
}
return a
}
var tmp = add()
print(tmp)
print(a)
打印输出:
1
2
6.2 实际应用
我们在做指针操作时,统一处理指针的晰构函数
let count = 2
let pointer = UnsafeMutablePointer<Int>.allocate(capacity: count)
pointer.initialize(repeating: 0, count: count)
defer {
pointer.deinitialize(count: count)
pointer.deallocate()
}
比如在网络请求的时候,可能有不同的分支进行回调函数的执行
func netRequest(completion: () -> Void) {
defer {
self.isLoading = false
completion()
}
guard error == nil else { return }
}
七、闭包的本质
还原出闭包结构体,(还原过程省略)
struct ClosureData<Box>{
var unkown: UnsafeRawPointer
var object: HeapObject<Box>
}
struct HeapObject{
var metadata: UnsafeRawPointer
var refcount1: Int32
var refcount2: Int32
}
struct Box<T>{
var object:HeapObject
var value: T
}
闭包本质是一个结构体,是引用类型,数据结构是:闭包的执行地址+捕获变量堆空间的地址