神刀安全网

简单理解 Block

一、万年不变老问题:什么是 Block

Block 是一段代码块,可以简单的理解为带有自动变量的 匿名函数自动变量 可以理解为 局部变量匿名函数 就是没有名字的函数。Block 可以像函数一样,传入参数,得到返回值

二、Block 的声明与定义

  1. Block 变量的声明

     int (^myBlock)(int);

    上述代码使用操作符 ^ 声明了一个名为 myBlock 的变量。入参为 int 类型,返回值为 int 类型。

  2. Block 的定义

     myBlock = ^(int num) {      return num * 2;  };

    ^ 操作符表示 Block 语句的开始;
    () 中为参数列表;
    {} 中是实现的实现的实体;
    于是我们就知道上面的代码在说什么了:传入参数 num,返回 int 类型的对象,赋值给 myBlock

    完整的分析可以看下图

    简单理解 Block
    1.jpg

  3. 与函数的不同

    1. 没有函数名;(匿名函数嘛~)
    2. 带有操作符 ^

三、Block 的使用

1. Block 的调用

好了,我们已经知道怎么声明及定义 Block,那么怎么使用呢?超简单,上面说了 Block 与 函数很像,想想我们是怎么使用函数的?

 NSLog(@"================%d", myBlock(3)); //结果为6

2. Block 作为函数参数

添加 typedef 关键字,声明一个 Block 类型变量。添加关键字的目的是,可以直接使用名称 nameBlock

typedef void(^nameBlock)(NSString *name);

nameBlock 作为入参,实现一个函数。

- (void)nameFunction:(nameBlock)nameBlock {     nameBlock(@"小井"); }

对于函数的使用,直接调用时:

[self nameFunction:^(NSString *name) {     if (![name isEqualToString:@""]) {         NSLog(@"My name is %@", name);     } }]; // 结果为 My name is 小井

四、Block 中变量的修改

声明一个变量 temp, 声明一个 testBlock,在 testBlock 的实现体中打印出变量 temp 的值。

int temp = 0; void (^testBlock)() = ^{     NSLog(@"temp = %d", temp);  }; temp = 1; testBlock(); //结果 temp = 0

从最后的打印结果可以看出,尽管在调用 testBlock 之前,对变量 temp 重新赋值为 1, 打印结果仍为 0。于是我们知道了:

Block 在访问外部变量时,会拷贝一份到自己的数据存储中。

为了证明我们的观点,分别在 testBlock 实现体内部和实现体外部打印下地址。

int temp = 0;   void (^testBlock)() = ^{     NSLog(@"temp = %d", temp);     NSLog(@"内部 temp is %ld", &temp); }; temp = 1; NSLog(@"外部 temp is %ld", &temp); testBlock();  //结果   // 外部 temp is 140734745021036  // temp = 0  // 内部 temp is 106102872376448

果然,地址不一样了。

下面我们尝试在 testBlock 中修改变量 temp 的值,在 testBlock 的实现实体中,添加如下一句代码。

temp = 2;

会发现,编译器报错了。这是因为 Block 拷贝的变量值是 const 的,即,在 Block 内部不能随意修改。但是当我确实有这样的需求,希望在 Block 内部修改外部变量时,怎么办呢?当当当~~~,只需要在外部变量的声明之前加上 __block 关键字,就可以愉快的在 Block 内部修改变量的值了,我们试试。

 __block int temp = 0;  void (^testBlock)() = ^{     temp = 2;     NSLog(@"temp = %d", temp);     NSLog(@"内部 temp is %ld", &temp); }; temp = 1; NSLog(@"外部 temp is %ld", &temp); testBlock(); // 结果 // 外部 temp is 106102872333208 // temp = 2 // 内部 temp is 106102872333208

我们可以发现,在 testBlock 内部成功的修改了变量 temp 的值,并且,跟之前不一样的是,这次的变量地址也相同的。因为加入了 _block 修饰符后,Block 不再拷贝原变量,而是拷贝原变量的引用地址,即这次是把指针拷贝了过来,指针指向原变量地址

简单理解 Block
2.jpg

五、Block 深坑之循环引用

block 的使用不当会造成循环引用,内存泄露。

简单理解 Block
3.jpg

typedef void (^block_t)(void);  @interface TestObject : NSObject {     block_t block_; } @end  @implementation TestObject  - (id)init {     self = [super init];     block_ = ^(void) {           NSLog(@"self = %@", self);     };     return self; }

上面的代码编译器会显示 warning

Capturing ‘self’ strongly in this block is likely to lead to a retain cycle;

block_self 的成员变量,self 持有 Block 的强引用。在 init 初始化方法中, Block 的实现体中,使用了 id 类型的 self, 赋值给了成员变量 block_ ,Block 语法自动由栈拷贝到堆,Block 持有了 self,于是造成了循环引用。当 main 函数结束时,由于循环引用的存在,堆上的对象不能释放,造成了内存泄露。如下图:

简单理解 Block
4.jpg

解决 Block 的内存泄露有两种办法:

  1. 使用 __block;
  2. 使用 __weak;

先说使用 __weak:声明 __weak 属性的临时变量 temp, 并将 self 赋值给临时变量。

- (id)init {     self = [super init];     id __weak temp = self;     block_ = ^(void) {         NSLog(@"self = %@", temp);     };     return self; }
简单理解 Block
5.jpg

再说使用 __block 方法:

- (id)init {       self = [super init];         __block id temp = self;      block_ = ^(void) {         NSLog(@"self = %@", temp);         temp = nil;     };     return self; }

可以分析出:

self 持有 Block;
Block 持有 __block 变量;
__block 变量持有 self;

简单理解 Block
6.jpg

从图上可以看出,还是会存在循环引用的。此时只需要显示的调用下 block_() 就能解决问题,因为在 Block 的执行体中,temp 变量被赋值为 nil, 对 self 的强引用失效,故解除了循环引用。

简单理解 Block
7.jpg

对于 Block 的简单的认知就说到这里,本文并没有讲深层的 Block 内存等概念,感兴趣的童鞋可以自己看下下面的参考书籍吧~。

参考书籍:《Objective-C 高级编程 iOS 与 OS X 多线程和内存管理》

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » 简单理解 Block

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址