文章目录

  • 前言
  • 1. Block简介
  • 2. 语法
    • 初始化和声明
  • 3. Block类型变量
    • typedef
    • 截获自动变量
    • --block
    • 截获的自动变量
  • 4.block的实现
    • 4.1 Block的存储域
        • 关键理解
      • 你知道几种block?
        • __NSGlobalBlock__
        • __NSMallocBlock__
        • __NSStackBlock__
      • 深入理解Block的存储域
        • 问题遗留
        • Blocks如何实现复制到堆上
        • 编译器何时需要手动copy?
    • 4.2__block变量的存储域
        • 在一个Block使用__block对象
        • 在多个Block使用__block对象
      • 问题解释
  • 截获对象
      • 问题引入
    • 源码理解![请添加图片描述](https://img-blog.csdnimg.cn/dde37719f5064266989d78e81e0cd50f.png)
      • `_main_block_copy_0`
      • `_main_block_depose_0`
      • 调用时机
      • 区分__block对象和普通截获对象
  • 7. __block变量和对象
    • __block
    • __weak试一试

前言

  • 小蓝书的Block说的东西实在是太少了,本篇博客复习和学习混搭,重点了解Block的基础和一些关键问题,针对于小白书的BLK部分进行基础学习

1. Block简介

  • block是一个带有自动变量值的匿名函数,它也是一个数据类型,跟int double float一样都是数据类型.所以我们是可以创建一个block类型的变量.

关键理解

  • 代码块,block类似一个于方法。而每一个方法都是在被调用的时候从硬盘到内存,然后去执行,执行完就消失,所以,方法的内存不需要我们管理,也就是说,方法是在内存的栈区。所以,block不像OC中的类对象(在堆区),他也是在栈区的。如果我们使用block作为一个对象的属性,我们会使用关键字copy修饰他,因为他在栈区,我们没办法控制他的消亡,当我们用copy修饰的时候,系统会把该 block的实现拷贝一份到堆区,这样我们对应的属性,就拥有的该block的所有权。就可以保证block代码块不会提前消亡

2. 语法

初始化和声明

 返回值类型 (^block变量的名称)(参数列表)
void (^block1)()//无返回值,无参数
int (^block2)(int num1,int num2)//int类型返回值i,两个int类型参数
  • block变量的赋值

     返回值(^block变量名)(参数列表) = ^(参数列表){函数体};
    
- (void)bkl2 {void (^block1)(NSString *a, NSString *b) = ^(NSString *x, NSString *y) {NSLog(@"%@--%@",x,y);};block1(@"S11", @"EDG");
}

3. Block类型变量

  • Block类型变量与一般C语言函数变量完全相同,可作为以下用于使用
    • 自动变量
    • 函数参数
    • 静态变量
    • 静态全局变量
    • 全局变量
  • 使用Block语法,将Block赋值为Block变量
int (^blk)(int) = ^(int count){return count + 1};
  • 因为与通常的变量相同,所以当然也可以有Block类型变量赋值给Block类型变量。
int (^bilk1)(int) = blk;
- (void)blk3 {int (^blk)(int) = ^(int count){return count + 1;};int (^blk1)(int) = blk;int (^blk2)(int);blk2 = blk1;}

typedef

  • 函数参数中使用Block类型变量可以向函数传递Block。但是在参数和函数返回值中使用Block类型变量极为复杂。这时,我们可以使用typedef来解决该问题
typedef int (^blk_t)(int);

截获自动变量

  • blk的带有自动变量的匿名函数,什么是带有自动变量?

    带有自动变量值”在Block中表现为“截取自动变量值
    
NSString *c = @"ff";
NSString* (^addBlock)(NSString *a, NSString *b) = ^(NSString *a, NSString *b) {return [NSString stringWithFormat:@"%@%@%@", a, b, c];
};

–block

自动变量值截获只能保存执行Block语法瞬间的值。
__不允许块属性,仅允许在局部变量上 __block attribute not allowed, only allowed on local variables
保存后就不能改写该值。若想在Block语法的表达式中将值赋给Block语法外声明的自动变量,需要在该自动变量上附加__block说明符。我们称这种变量为__block变量。

截获的自动变量

  • 对于块来说 截获Objective-C对象,调用变更该对象的方法不会产生编译错误
- (void)bll4 {id tstArray = @[@"blk", @"不允许赋值", @"给截获的变量"];id array = [[NSMutableArray alloc] init];void (^blk)(void) = ^{id obj = [[NSObject alloc] init];[array addObject:obj];};}

而向截获的变量array赋值则会产生编译错误。

- (void)bll4 {id tstArray = @[@"blk", @"不允许赋值", @"给截获的变量"];id array = [[NSMutableArray alloc] init];void (^blk)(void) = ^{id obj = [[NSObject alloc] init];//        [array addObject:obj];array = tstArray;};}

4.block的实现

  • 对于blk实现部分的小白书的前三点我在这篇博客不写了,这边博客是先了解基础,接下来会再去了解源码之后作新的博客学习。

4.1 Block的存储域

关键理解

  • 代码块,block类似一个于方法。而每一个方法都是在被调用的时候从硬盘到内存,然后去执行,执行完就消失,所以,方法的内存不需要我们管理,也就是说,方法是在内存的栈区。所以,block不像OC中的类对象(在堆区),他也是在栈区的。如果我们使用block作为一个对象的属性,我们会使用关键字copy修饰他,因为他在栈区,我们没办法控制他的消亡,当我们用copy修饰的时候,系统会把该 block的实现拷贝一份到堆区,这样我们对应的属性,就拥有的该block的所有权。就可以保证block代码块不会提前消亡。
  • Block 和 ——block变量的实质
  • block既然是OC的对象,那么也存在许多的类似的类

你知道几种block?

block的类

  • 书上的关于存储区域分配的解释
    • 三种类对应了三种不同的区域

NSGlobalBlock

- (void)globaBlock {void (^glbBlk)(void) = ^{NSLog(@"1");};NSLog(@"%@", glbBlk);
}![请添加图片描述](https://img-blog.csdnimg.cn/27a0a020364241f785483e8e3f52b394.png)
  • 从打印结果NSGlobalBlock可以看出来,这是一个全局的Block。
  • 从代码上来看,这是一个无参数也无返回值的Block,Block体内什么也没有做,也没有引用任何的变量,总结来说如下
    GlobalBlock:
    • 位于全局区
    • Block内部不使用外部变量(不使用应截获的自动变量),或者只使用静态变量和全局变量

NSMallocBlock

- (void)mallocBlock {NSString* str = @"mallocBlk";void (^glbBlk)(void) = ^{NSLog(@"%@", str);};NSLog(@"%@", glbBlk);
}

  • 这里引用了一个外部的变量,打印是NSMallocBlock,也就是堆Block
  • NSMallocBlock:
    • 在Block内部使用局部变量或者OC属性,并且赋值给强引用或者Copy修饰的变量。

NSStackBlock

- (void)stackBlock {NSString* str = @"stackBlk";void (^__weak jpBlock)(void) = ^{NSLog(@"输出:%@", str);};NSLog(@"%@",jpBlock);}

  • NSStackBlock 和 NSMallocBlock这里代码的区别就是在ARC环境下使用了__weak修饰符
    • block不像OC中的类对象(在堆区),他也是在栈区的。如果我们使用block作为一个对象的属性,我们会使用关键字copy修饰他,因为他在栈区,我们没办法控制他的消亡,当我们用copy修饰的时候,系统会把该 block的实现拷贝一份到堆区
  • 所以总结 NSStackBlock:
    • 位于栈区
    • 与MallocBlock一样,可以在内部使用局部变量或者OC属性。
    • 但是不能赋值给强引用或者Copy修饰的变量()
  • 我认为 NSStackBlock 和 NSMallocBlockd的区别就是 NSMallocBlockd通过一些修饰把 NSStackBlock 从栈变成了OC的属性,从而就到了堆

深入理解Block的存储域

  • 刚才的栈区和堆区的理解是我个人的猜测,书上给了大量的解释和说明。
  • 为什么Block超出变量作用区域可以存在
    配置在全局变量上的 Block,从变量作用域外也可以通过指针安全地使用。但放置在栈上的Block,如果其所属的变量作用城结束,该Blook 就被废弃。由于__block 交量也配置在栈上,同样地,如果其所属的变量作用域结束,则该_block 变量也会被废弃
    Blocks提供了把Block和__block复制到堆上的方法,防止block被废弃

    复制到堆上的block将NSMallocBlock类对象写入Block用结构体实例的成员变量指针

问题遗留

这里还没有深入了解block的内部结构,下一篇博客深入探讨Block源码

Blocks如何实现复制到堆上

  • 对于OC来说,当ARC处于有效情况的时候,大多数情况下编译器会自己进行判断,自动生成把block从栈复制到堆上的代码。
    • 以返回block的函数为例
blk_t func(int rate) {return ^(int count) {return rate * count;};
};
- (void)testBlk {func(4);NSLog(@"%@", func(4));
}
  • 该代码本应该是返回配置在栈上的block函数当程序结束的时候block应该被废弃,但实际打印出来的结果是malloc block


书上讲该段代码翻译成源代码

blk_t func(int rate) {return ^(int count) {return rate * count;};
};

  • 在ARC处于有效状态下,blk_t tmp 和blk_t strong tmp是一样的效果,也就是

  • 源码的 objc_retainBlock(tmp)其实就是Block_copy函数
tmp = Block_copy(tmp)
return objc_autoReleaseReturnValue(tmp)
  • 翻译源码的注释看实现栈到堆的过程发生了什么

  • 也就当Block作为函数的返回值返回的时候,编译器会自动生成复制到堆上的代码。

编译器何时需要手动copy?

  • 大多数情况⬇️编译器会自主的进行判断拷贝,但是如下情况需要手动copy。
    • 向方法或者函数里传递Block时。
  • 但是在方法或者函数里面适当的复制了传递的参数时,那么就不需要手动复制
    • GCD的API不需要
    • cocoa框架方法且方法名包含usingBlock

对于Block语法和Block类型变量都可以直接调用copy方法

blkCopy = [blk copy];

对于配置在栈上的Block我们知道调用copy方法会讲Block从栈复制到堆上,那么存储在数据域和堆上Block调用copy会发生如下情况

在不确定的情况下调用copy方法不会出现问题,即使是引用计数也有ARC帮我们自动管理。

多次调用copy也不会出现问题

 int (^blk)(int) = ^(int count){return count + 1;};blk = [[[[blk copy] copy] copy] copy];

多次的copy就是一个持有,废弃,持有,废弃。。。的重复过程,对于ARC我们有自动管理,所以不会出现问题。

4.2__block变量的存储域

__block变量的存储域和引用计数的模式类似,因为__block对象是在Block里被引用或者调用的,当Block被从栈复制到堆时,__block也有一样的变化

在一个Block使用__block对象

在一个Block中使用__block对象的时候,若Block从栈复制到堆,__block对象也从栈复制到堆,此时Block持有__block

若是Block本就在堆上,此时对__block没有影响;

在多个Block使用__block对象

多个Block使用__block对象的时候,当一个Block从栈复制到堆,此时和一个Block情况一样,当其余Block从栈复制到堆的时候,Block会持有__block对象,此时__block对象引用计数加1;
当Block被废弃,那么它持有的__block对象也会被释放,这个是一一对应的关系,__block的思考方式和OC的引用计数内存管理方式完全相同

问题解释

  • “不论__block变量配置在栈上还是堆上,都能够正确的访问该变量”

通过Block的复制,可以同时访问堆和栈的__block变量。因为此时__block也从栈复制到堆,这与__block结构体使forward ing指针变量有关。

理解:当栈上的__block被复制到堆上的时候,栈上的__block变量用结构体实例在__block变量从栈复制到堆上的时候,_forwarding成员变量的值从指向栈上的自身替换为目标堆上__block变量用结构体实例的的地

截获对象

再次声明概念

  • 在 ARC 中,捕获了外部变量的 block 的类会是 NSMallocBlock 或者 NSStackBlock,如果 block 被赋值给了某个变量,在这个过程中会执行 __Block__copy 将原有的 NSStakeBlock 变成 NSMallocBlock ;但是如果 block没有赋值给某个变量,那他的类型就是 NSStakeBlock ;没有捕获外部变量的 block 的类会是 NSGlobalBlock 即不再对上,也不在栈上,它类似 C 语言函数一样会在代码段中。

  • 在非 ARC 中,捕获了外部变量的 block 的类会是 NSStackBlock ,放在栈上,没有捕获外部变量的 block 时与 ARC 环境下情况相同。

问题引入

  • 书上展示了如下的代码
typedef void (^blk_t1) (id);
//  截获对象
- (void)blkObj {id array = [[NSMutableArray alloc] init];blk_t1 blk;blk = [^(id obj) {[array addObject:obj];NSLog(@"array count = %ld", [array count]);} copy];blk([[NSObject alloc] init]);blk([[NSObject alloc] init]);blk([[NSObject alloc] init]);NSLog(@"%@", blk);
}

代码首先生成并持有NSMutableArray对象,由于附有__strong目标变量的作用域立即结束,那么该对象应该被废弃,其强引用失效并释放,但是代码打印结果如图

结果表示可变数组的类对象在Block的执行过程超出了变量的作用域而存在。

源码理解

被赋值的自动变量array对象发现其在Block结构体中是带有__strong关键字的成员变量,书上的解释是OC库能够很准确的把握从栈复制到堆,以及堆上被废弃的Block 因此即使结构体的成员变量含有__strong或者__weak关键字修饰的变量,编译器也可以适当的初始化和废弃。

Block源码里出现了_main_block_copy_0 _main_block_depose_0两个关键函数。

_main_block_copy_0

因为带有_-strong修饰符,所以_main_block_copy_0 函数调用 _Block_object_assgin函数来持有该array对象

_Block_object_assgin函数 相当于retain实例方法,将对象赋值在对象类型的结构体成员变量当中。

_main_block_depose_0

_main_block_depose_0函数调用_Block_object_dispose,释放赋值在Block用结构体成员变量array中的对象。
_Block_object_dispose函数 相当于release实例方法,释放在对象类型的结构体成员变量中的对象。

调用时机

Block复制到堆上的情况

  • 调用 Block 的 copy 实例方法时
  • Block 作为函数返回值返回时
  • 将 Block 赋值给附有strong 修饰符 id 类型的类或 Block 类型成员变量时
  • 在方法名中含有usingBlock 的 Cocoa 框架方法或 Grand Central Dispatch 的 API 中传递
    Block 时

其表面看来是从栈栈复制到堆,本质是当调用到了_Block_copy函数的时候从栈复制到堆

相对的就是释放的时候,对于谁都不持有的对象会调用_Block_object_dispose函数,相当于dealloc方法
所以解释了上述截获对象array为什么可以超出其变量作用域而存在。

区分__block对象和普通截获对象

在Block里,系统需要区分使用上述copy或者depose函数的对象是对象还是__block对象

截获对象时和使用 __block变量的不同

通过 _BLOCK_FIELD_IS_OBJECT 和 _BLOCK_FIELD_IS_BYREF参数,区分copy函数和dispose函数的对象类型是对象还是__block变量

copy函数持有截获的对象和__block变量,dispose函数释放截获的对象和__block变量,所以通过__strong修饰的__block变量被堆上的BLOCK持有,因而超出了变量的作用域而还可以存在

在Block里使用对象类型的自动变量时,除了以下情形意外,推荐手动copy实例方法

7. __block变量和对象

__block说明符可以指定任何类型的自动变量。

__block id obj = [[NSObject alloc] init];

该代码等同于

__block id __strong obj1 = [[NSObject alloc] init];

ARC有效的时候 id类型以及对象类型变量必定附加所有权修饰符。

__block

该代码通过clang翻译源码如下

上述出现了_Block_object_assgin函数和_Block_object_dispose函数。

当在Block使用附有strong修饰符修饰的id类型或对象类型的自动变量时,当Block从栈复制到堆的时候,使用_Block_object_assgin函数,持有被捕获对象,当堆上的Block被废弃的时候使用_Block_object_dispose函数,释放截获对象。

对于__block变量会发生相同的事情。所以即使对象赋值到堆上的附有strong修饰符的对象类型的__block变量中,只要__block变量在堆上继续存在,那么该对象就会继续处于被持有的状态。

__weak试一试

对于之前的array代码,我们加入__weak修饰符试试.
这里的代码和书上的有所不同,按书上的我们需要重新初始化array释放,达到效果。

 array = [[NSMutableArray alloc] init];
- (void)blkWeak {void(^blk)(id);id array = [[NSMutableArray alloc] init];id weakArray = array;array = [[NSMutableArray alloc] init];blk = [^(id obj) {[weakArray addObject:obj];NSLog(@"array count = %ld", [weakArray count]);} copy];blk([[NSObject alloc] init]);blk([[NSObject alloc] init]);blk([[NSObject alloc] init]);NSLog(@"%@", blk);
}

同时加入__block试一试

 id __weak __block weakArray = array;

这是因为即使附加了__block说明符,附有__strong修饰符的变量array也会在该变量作用域结束的同时被释放被废弃,nil被赋值给附有__weak修饰符的变量的weakArray中。

[iOS - Block基础再探究]相关推荐

  1. IOS开发基础之OC的Block入门_Day09-Block

    IOS开发基础之OC的Block入门_Day09-Block block是oc的重要的基础知识,重点之重.跟协议一样重要,是进行函数回调重要手段.在后续的UI学习具有举足轻重的地位.学会基础的bloc ...

  2. iOS开发基础知识--碎片44

    iOS开发基础知识--碎片44  iOS开发基础知识--碎片44 1:App跳转至系统Settings 跳转在IOS8以上跟以下是有区别的,如果是IOS8以上可以如下设置: NSURL *url = ...

  3. iOS 动画基础总结篇

    iOS 动画基础总结篇   动画的大体分类(个人总结可能有误) 分类.png UIView 动画 属性动画 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 1 ...

  4. iOS 面试基础题目

    转载: iOS 面试基础题目 题目来自博客:面试百度的记录,有些问题我能回答一下,不能回答的或有更好的回答我放个相关链接供参考. 1面 Objective C runtime library:Obje ...

  5. 计算机网络基础ios指令,蔡少云——计算机网络实验:IOS命令基础及交换机基本配置.doc...

    <计算机网络实验>实验报告 学 院 管理学院 专 业 电子商务 年级班别 2013级1班 学 号 3213004774 学生姓名 蔡少云 指导教师 黄益民 成 绩_____________ ...

  6. IOS开发基础之网易新闻UICollectionView的使用第3天

    IOS开发基础之网易新闻UICollectionView的使用第3天 由于第3天的UICollectionView 并不实现,我查阅相关资料,也没解决,先从本地的plist加载的数据,不是网络的上的数 ...

  7. IOS开发基础之网易新闻环境搭建异步请求json,AFN网络封装第1天

    IOS开发基础之网易新闻环境搭建异步请求json,AFN网络封装第1天 视频资料是2015年的,但是AFN是导入框架的关键文件,我尝试使用cocoapods安装最新的AFN,虽然成功了,但是版本太高, ...

  8. IOS开发基础之模拟科技头条项目案例32

    IOS开发基础之模拟科技头条项目案例32 说说这个项目的技术要点核心 ,首先是异步网络请求,block的回调,sdWebImage的使用.各个控件的使用,NSDate日期的转换.自动适配屏幕工作,模型 ...

  9. IOS开发基础之异步下载网络图片第1部分

    IOS开发基础之异步下载网络图片第1部分 加入ATS // LJAppInfo.h // 37-异步下载网络图片 // Created by 鲁军 on 2021/3/10. #import < ...

最新文章

  1. 客户端(C#)调用CXF搭建的webservice的出现一些问题记录
  2. Spark Streaming事务
  3. 举例什么时候会用到 call(), apply()
  4. [置顶]信息发布系统 Jquery+MVC架构开发(4)Model 层
  5. [Erlang危机](5.1.1)内存
  6. cuda-convnet在Ubuntu12.04+CUDA5.5下的配置
  7. P3694-邦邦的大合唱站队【状压dp】
  8. php集成环境还需要mysql吗_是选择php集成环境好还是分开安装的原生版好
  9. HelloDjango 第 04 篇:Django 迁移、操作数据库
  10. hal库串口dma卡死_HAL库版DMA循环模式串口数据收发
  11. 如何使用MacClean在Mac上释放磁盘空间?
  12. File类的基本操作方法
  13. C语言小游戏(一)----猜数游戏
  14. CANoe下载地址以及CAN Demo 16的下载与激活,并附录所有CANoe软件版本
  15. ajax 后面接什么,什么是AJAX?
  16. python代码计算字数_如何用python计算文件的字数
  17. QT的.Pro文件在哪儿找帮助手册
  18. 你只要上传两张毫无关联的照片「这个网站就会自动帮你合成了!」
  19. 技巧心得:网络工程师考试大纲
  20. 评测 i5 13600kf和r7 5800x3D差距 酷睿i513600kf和锐龙r7 5800x3D选哪个好

热门文章

  1. 测试用例编写方法—等价类
  2. 某校大学生使用计算机情况,关于大学生电脑使用情况的调查报告
  3. 双层for循环中包含查询的优化
  4. 【Unity植物大战僵尸】第二个植物豌豆射手(九)
  5. 【音视频基础】多媒体文件格式
  6. Android View体系(3)
  7. 解决:org.springframework.web.multipart.MultipartException: Failed to parse multipart servlet request;
  8. autojump是什么
  9. ARM嵌入式编译器-volatile关键字对编译器优化的影响
  10. 零基础CSS入门教程(9)——背景颜色和背景图片