Swfit进阶-13-Swift中的闭包的的捕获

289 阅读3分钟

「这是我参与2022首次更文挑战的第14天,活动详情查看:2022首次更文挑战」。

  • 本文主要介绍Swift中闭包的捕获

1. 捕获值

我们在学习闭包捕获值的时候可以回顾下OC中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);

}

打印的是结果是2,1,2。说明编译的时候已经捕获了i=1的情况,此时如果我们改变i不会对捕获的变量产生影响,类似值拷贝。那么如果我们想要外部的修改能够影响当前 block 内部捕获的值,我们只需要对当前的 i 添加__block修饰符

- (void)testBlock{ 
__block NSInteger i = 1;

void(^block)(void) = ^{ 
i += 1;
NSLog(@"block %ld:", i)
};

i += 1;
NSLog(@"before block %ld:", i); 
block();
NSLog(@"after block %ld:", i);

}

这里相当于指针拷贝,捕获的变量可以指向外部i的内存空间,相当于对同一片内存空间进行操作.

  • 那么我们在Swift中写类似的代码
    var i = 1


    let closure = {

        print("closure:\(i)")

    }


    i += 1


    print("before closure:\(i)")


    closure()


    print("after closure:\(i)")

打印的结果

image.png

说明和我们的block有一定区别,这里直接使用外部的变量i。我们编译下查看sil文件

image.png

可以发现当前的i并不是自己变量,而是拿到全局变量的地址,之后取出里面的值进行打印。

另外我们知道 OC中block有全局block栈block堆block,但是在Swift的闭包没有这个概念。我们打印不管是全局变量还是局部变量,存储的都是metadata

image.png

2. 探究捕获值

我们定义一个一个函数,里面有个闭包

image.png

可以看出,打印的结果每次都是在上次函数执行的基础上累加的,但是我们所知的runningTotal是一个临时变量,按理说每次进入函数都是10,这里为什么会每次累加呢?

image.png

我们可以类似于类来理解,上面的函数中的闭包捕获了变量runningTotal,之后再次调用函数的时候相当于对捕获的变量进行操作。

image.png

当我们重新定义这个函数调用的时候,就相当于重新调用。

2.1 SIL分析

image.png

我们看下makeIncrementer的实现,这里可以发现alloc_box,我们查看官方文档说明:在堆区创建一个足够大的空间容纳T类型。而project_box,则是把创建的T类型的对象放到盒子中。因此我们上面的闭包相当于把runningTotal拷贝到堆区,它就变成了实例对象的一部分。 因此我们定义的makeInc就是同一实例对象的堆空间地址,而我们makeIncrementer()每次创建都是一个新的实例对象堆空间地址。

2.2 小结

  • 一个闭包能够从上下文捕获已经定义的常量和变量,即使这些定义的常量和变量的原作用域不存在,闭包仍然能够在其函数体内引用和修改这些值
  • 当每次修改捕获值时,修改的是堆区中的value值
  • 当每次重新执行当前函数时,都会重新创建内存空间

3. OC Block 和 Swift闭包相互调用

我们在OC中定义的Block,在Swift中是如何调用的那我们来看一下

typedef void(^ResultBlock)(NSString *message);
 @interface LGTest : NSObject
+ (void)testBlockCall:(ResultBlock)block;
@end
//实现
+(void)testBlockCall:(ResultBlock)block

{

    if (block) {

        block(@"hello");

    }

在 Swift 中我们可以这么使用

LGTest.testBlockCall { message in

    let errorcast = message as String

    print(errorcast)

}

我们也可以自己回调使用这个oc的Block

func testBlock(_ block :ResultBlock){

    let error = "not Found"

    block(error)

}

testBlock { message in

    print(message)

}

我们在 Swift里这么定义,在OC中也是可以使用的

class LGPerson: NSObject {

    @objc static var closure: (() -> ())?

}

+(void)testBlockCall:(ResultBlock)block

{

    

    //if (block) {

        

       // block(@"hello");

    //}

    

    LGPerson.closure = ^{

        

        NSLog(@"hello closure");

    };

    

    LGPerson.closure();

    

}