[iOS - Block基础再探究]
文章目录
- 前言
- 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的执行过程超出了变量的作用域而存在。
源码理解![](/assets/blank.gif)
被赋值的自动变量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基础再探究]相关推荐
- IOS开发基础之OC的Block入门_Day09-Block
IOS开发基础之OC的Block入门_Day09-Block block是oc的重要的基础知识,重点之重.跟协议一样重要,是进行函数回调重要手段.在后续的UI学习具有举足轻重的地位.学会基础的bloc ...
- iOS开发基础知识--碎片44
iOS开发基础知识--碎片44 iOS开发基础知识--碎片44 1:App跳转至系统Settings 跳转在IOS8以上跟以下是有区别的,如果是IOS8以上可以如下设置: NSURL *url = ...
- iOS 动画基础总结篇
iOS 动画基础总结篇 动画的大体分类(个人总结可能有误) 分类.png UIView 动画 属性动画 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 1 ...
- iOS 面试基础题目
转载: iOS 面试基础题目 题目来自博客:面试百度的记录,有些问题我能回答一下,不能回答的或有更好的回答我放个相关链接供参考. 1面 Objective C runtime library:Obje ...
- 计算机网络基础ios指令,蔡少云——计算机网络实验:IOS命令基础及交换机基本配置.doc...
<计算机网络实验>实验报告 学 院 管理学院 专 业 电子商务 年级班别 2013级1班 学 号 3213004774 学生姓名 蔡少云 指导教师 黄益民 成 绩_____________ ...
- IOS开发基础之网易新闻UICollectionView的使用第3天
IOS开发基础之网易新闻UICollectionView的使用第3天 由于第3天的UICollectionView 并不实现,我查阅相关资料,也没解决,先从本地的plist加载的数据,不是网络的上的数 ...
- IOS开发基础之网易新闻环境搭建异步请求json,AFN网络封装第1天
IOS开发基础之网易新闻环境搭建异步请求json,AFN网络封装第1天 视频资料是2015年的,但是AFN是导入框架的关键文件,我尝试使用cocoapods安装最新的AFN,虽然成功了,但是版本太高, ...
- IOS开发基础之模拟科技头条项目案例32
IOS开发基础之模拟科技头条项目案例32 说说这个项目的技术要点核心 ,首先是异步网络请求,block的回调,sdWebImage的使用.各个控件的使用,NSDate日期的转换.自动适配屏幕工作,模型 ...
- IOS开发基础之异步下载网络图片第1部分
IOS开发基础之异步下载网络图片第1部分 加入ATS // LJAppInfo.h // 37-异步下载网络图片 // Created by 鲁军 on 2021/3/10. #import < ...
最新文章
- 客户端(C#)调用CXF搭建的webservice的出现一些问题记录
- Spark Streaming事务
- 举例什么时候会用到 call(), apply()
- [置顶]信息发布系统 Jquery+MVC架构开发(4)Model 层
- [Erlang危机](5.1.1)内存
- cuda-convnet在Ubuntu12.04+CUDA5.5下的配置
- P3694-邦邦的大合唱站队【状压dp】
- php集成环境还需要mysql吗_是选择php集成环境好还是分开安装的原生版好
- HelloDjango 第 04 篇:Django 迁移、操作数据库
- hal库串口dma卡死_HAL库版DMA循环模式串口数据收发
- 如何使用MacClean在Mac上释放磁盘空间?
- File类的基本操作方法
- C语言小游戏(一)----猜数游戏
- CANoe下载地址以及CAN Demo 16的下载与激活,并附录所有CANoe软件版本
- ajax 后面接什么,什么是AJAX?
- python代码计算字数_如何用python计算文件的字数
- QT的.Pro文件在哪儿找帮助手册
- 你只要上传两张毫无关联的照片「这个网站就会自动帮你合成了!」
- 技巧心得:网络工程师考试大纲
- 评测 i5 13600kf和r7 5800x3D差距 酷睿i513600kf和锐龙r7 5800x3D选哪个好
热门文章
- 测试用例编写方法—等价类
- 某校大学生使用计算机情况,关于大学生电脑使用情况的调查报告
- 双层for循环中包含查询的优化
- 【Unity植物大战僵尸】第二个植物豌豆射手(九)
- 【音视频基础】多媒体文件格式
- Android View体系(3)
- 解决:org.springframework.web.multipart.MultipartException: Failed to parse multipart servlet request;
- autojump是什么
- ARM嵌入式编译器-volatile关键字对编译器优化的影响
- 零基础CSS入门教程(9)——背景颜色和背景图片