Swift闭包

197 阅读5分钟
一、函数类型
1.1 示例

函数本身也有自己的类型,它由形 式参数类型,返回类型组成。

func addTwoInts(_ a: Int, _ b: Int) -> Int{
    return a + b
}

var m = addTwoInts

print(m(10,20))

打印输出:
30

如果有两同名不同参数类型的函数,必须指明参数类型

image.png

正确如下:

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代码

image.png

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文件

image.png

编译过的.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
}

闭包本质是一个结构体,是引用类型,数据结构是:闭包的执行地址+捕获变量堆空间的地址