最近看了很多篇优秀的关于Block的文章, 本宝宝也来写一篇咯!如有雷同, 那正是我辛辛苦苦搬过来的. 我是一只搬砖酱, 搬砖本领强! 言归正传, 老司机要开车(≧▽≦)/啦啦啦 宝宝们自带小板凳儿快上车~~~
Block究竟是什么
我们通过clang(LLVM编译器)转换为我们可读源代码.
clang -rewrite -objc 源代码文件名
下面我们转化Block语法:
int main () {
void (^block)(void) = ^{
NSLog(@"block");
};
return 0;
}
转化的代码内容很多, 我们看看其中的一部分:
struct __main_block_impl_0{
void *isa; //类指针 指向 block 的 Class
int Flags; //保存 Block 的引用计数,所处位置(堆上还是栈上或者数据区),是否有拷贝函数等信息
int Reserved; //保留变量
void *FuncPtr; //函数指针, 指向 Block 的实现
struct __main_block_desc_0* Desc;//描述信息
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags = 0){
impl.isa = &_NSConcreteStrackBlock;//_NSConcreteStrackBlock 相当于class_t的结构体实例, 将Block指针赋值给Block的结构体成员变量isa
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}//结构体的构造函数
};
我们可以看到__main_block_desc_0也是一个结构体, 我们看看里面有哪些东西:
static struct __main_block_desc_0 {
size_t reserved; //保留变量
size_t Block_size; // Block 的大小
};
其实Block内部是一个结构体.本身就是 OC 对象.
什么是带有局部变量的匿名函数?
顾名思义, 所谓匿名函数就是不带有名称的函数." 带有局部变量值"在 Block 中表现为"截取局部变量值". eg:
int main ( )
{
int val = 10;
const char *fmt = "val = %d\n";
void (^block)(void) = ^ {
printf(fmt,val);
};
val = 2;
fmt = "These values were changed. val =%d\n ";
block();
return 0;
}
该代码执行结果:
val = 10
可以看到执行结果并不是改写后的值 "These values were changed. val = 2 ", 当执行Block语法时, 字符串指针"val = %d\n" 被赋值到局部变量fmt中, int 值10被赋值到局部变量 val 中, 这些值被保存(即被截获). 即使修改了 Block 中使用的局部变量值也不会影响 Block 执行时局部变量的值. 这就是局部变量值的截获.
那么问题来了,如何修改 block 内部截获的局部变量值呢?
解决截获 Block 内部截取变量值的办法有两种, 一是 C 语言中的变量, 另一种是 __block 修饰符.
__Block修饰符
__Block修饰符类似于static、auto、register说明符, 用于指定将变量值设置到哪个储存域中, eg:
-
auto 作为局部变量存储在栈中
-
static 表示作为静态变量存储在数据区中
-
__block 指定 Block 中想变更值的局部变量
__Block int val = 10; void (^block)(void) = ^{ val = 1; };
我们来看看加上__Block修饰符后,源代码会发生哪些变化:
struct __Block_byref_val_0{
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val; //原局部变量
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val;
__main_Block_impl_0(void *fp, struct __main_Block_desc_0 *desc, __Block_byref_val_0 *val, int flags = 0) :val(val->_forwarding){
impl.isa = &_NSConCreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself){
__Block_byref_val_0 *val = _cself->val;
(val -> __forwarding->val) = 1; //__Block_byref_val_0结构体实例的成员变量__forwarding指向该实例自身的指针
}
static void __main_block_copy_0 (struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src){
_Block_Object_assign(&dst->val,src->val,BLOCK_FIELD_IS_BYREF);
}
static void __main_block_dispose_0(struct __main_block_impl_0 *src){
_Block_Object_dispose(src->val, BLOCK_FIELD_IS_BYREF);
}
static struct __main_block_desc_0{
unsigned long reserved;
unsigned long Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
}__main_block_desc_0_DATA ={
0,
sizeof(struct __main_block_impl_0),
__main_block_copy_0,
__main_block_dispose_0
};
int main(){
__Block_byref_val_0 val ={
0,
&val,
0,
sizeof(__Block_byref_val_0),
10
};//__block修饰的变量
block = &_main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA,&val,0x22000000);
return 0;
}
只是在局部变量前附加了____block __修饰符,源代码量就剧增. 我们发现 ____block__ 变量 __val__ 变为了结构体实例. ____block 变量__也变成了结构体类型的局部变量 ____Block_byref_val_0__ , 变成了栈上生成的 ____Block_byref_val_0__ 结构体实例.
Block存储域
block与__block变量的实质.png Block源代码中的类.png变量作用域结束时, 栈上的 ____block 变量和 Block __也被废弃, 但是将配置在栈上的 Block 复制一份到堆上, 即使变量作用域结束, 堆上的 __block 变量和 Block 不受影响.
____block 变量用结构体成员变量 ____forwarding 可以实现:
无论 ____block 变量配置在栈上还是堆上都可以正确访问 ____block 变量. ( copy 到堆上后栈上的 ______forwarding 指向 copy 指到堆上的那份~)如图:
栈上的__forwarding复制后指针变化.png
PS: ARC状态下, 不管 Block 配置在何处, 用 copy 方法复制都不会引起任何问题, 在不确定时调用 copy 函数就好啦~
__block变量存储域
使用 ____block 变量__的 Block 从栈复制到堆上时, __block 变量也会受到影响.
Block从栈复制到堆时对__block变量产生的影响.png多个Block中使用 ____block变量__ 时, 最先的 Block 从栈复制一份到堆上, 当然______block变量也会复制一份到堆上并被 Block 持有. 而剩下复制一份到堆上的 Block, 不仅持有______block 变量并且增加 __block 变量的引用计数.
block相关面试题
1.可变数组需要用 ______block 修饰符吗?为什么?
不需要,因为数组的指针并没有变(往数组里面添加对象,指针是没变的,只是数组里对象指针指向的内容变了
2.Block、Delegate、通知的区别是什么?
使用场景: Block 用于动画,数据请求回调,枚举回调,多线程GCD等.. Delegate 用于公共接口,方法较多时用 Delegate 进行解耦等.. 通知适用于多个控制器间需要知道一个事件,相隔多层的控制器之间跳转等..
* Delegate 和通知一般都是一对一的通信,通知一般是一对多之间的通信;
* Delegate 需要定义协议方法,代理对象实现协议方法,并要建立代理关系才能实现通信;
* Block 代码写在 Block 块中更加简洁清晰,通信事件较多情况,建议使用 Delegate;
* Delegate 运行成本低, 效率比 通知高, Block 运行成本高。Block 出栈需要将使用的数据从栈内存拷贝到堆内存,对象的情况引用计数增加,使用完或者 Block 置 nil 后才消除;Delegate 只是保存了一个对象指针,直接回调,没有额外消耗。相对C的函数指针,只多做了一个查表动作;
* Delegate 注重信息传输的过程,Block注重信息传输的结果.比如发起一个网络请求,想知道请求是否已经开始、是否收到了数据、数据是否已经接受完成、数据接收失败时建议使用 Delegate ;对于一个事件,只想知道成功或者失败,而不需要知道其他信息则建议使用Block
* 一个 viewController 中有多个弹窗事件,Delegate 需要对每个事件进行判断识别来源, Block 可以在创建事件时区分。
* 代理执行协议方法时要使用 respondsToSelector 检查其代理是否符合协议(检查对象能否响应指定的消息),以避免代理在回调时因为没有实现方法而造成程序崩溃, Block 使用时要注意防止循环引用,通知一旦接收消息的对象多了,可读性差,不易维护.创建观察者一定要dealloc中移除.
3.Block用assign和retain修饰符会出现什么问题?
会发生Crash
为什么会Crash?
因为 MRC 下 Block 生成是在栈上的,只有 copy 函数能把栈上的 block 复制一份到堆上,这样不会导致 block 出了作用域就会被释放的问题.堆上的 Block 可以由我们自己手动管理, retain 对栈上的 Block 是没用的
4.MRC下, ______block 修饰变量为什么可以打破循环?
因为MRC状态下, __block 变量是在 Block 中引入一个新的结构体成员变量指向这个__block变量,这样 Block 捕获的id变量不会被retain. Block 中使用的变量没有 __block 修饰的对象或者局部变量,就会被retain.造成循环引用.
5.____block__ 修饰符什么时候使用?
在Block里面修改局部变量的值都要用__block修饰符
6.什么时候需要在 Block 中使用 weakSelf/strongSelf ?
在 Block 内如果需要访问 self 的方法、变量,防止循环引用使用 weakSelf。如果在 Block 内需要多次访问 self ,保证不被释放则需要使用 strongSelf.
7.使用 Block 如何避免循环引用 ?
根据不同情况可以使用 __block 变量、__weak 修饰符或 __unsafe_unretained 修饰符来避免循环引用。
感谢小笨狼哥哥和师父以及我们TeamiOS成员的悉心指导,上面代码都是手打的,如有错误,请大家及时指出.非常感谢~
网友评论