iOS Block 语法及底层实现
前言
iOS开发的同学们肯定都用过block,对block 的运用熟练在我们开发过程中很有必要;在此我也对其进行一番解读,希望能在大家开发或者面试过程中有所帮助。
一、block 概要
1、什么是block
一句话:带有自动变量(局部变量)的匿名函数。
Q1 什么 是匿名函数 度娘解释曰 ;
Q2 自动变量相关:C语言函数中可能用到的变量由自动变量、函数参数、静态变量、静态全局变量、全局变量;有兴趣的还可以去探究其内存中的存储域
二、block用法定义
1、block语法及变量
block 语法: ^ 返回值类型 参数列表 表达式
^ (NSString *) (NSString *bookId){}
省略返回值写法:
^(void){};
省略返回值及参数写法:
^{};
2、block 实质
简单些一行block
声明一个block并打印
void(^block)(void)=^{
printf("block block \n");
};
block();
通过在终端命令行 clang -rewrite-objc 源代码文件名 ,会生成相应的.cpp文件,由C++编写的源文件。具体实现大家可以自己去操作一下我这里贴出一段类似代码:
C++ 源码文件中很多内容,只要看到截图的调用部分。
block实质也是一个oc对象,简单来说和class代码的设计思想是为其构建一个结构体struct,把结构体相应参数做索引,遵循一定的规则,去找到相应的实现方法;同样的我们在设计OC 对象时也应借助这个思想。无论对象怎么变化,他有一个基本的结构体单元。
三、block截获自动变量
1、block底层如何截获自动变量值
上文中讲到block实际是一个oc 对象,它就有isa指针、变量等对象属性,当执行block语句中有使用外部变量时,会将相应的变量值 自动声明并存储到block 的结构体当中去
2、blcok 捕获自动变量
__block 修饰词修饰变量,类似于static、auto 等c语言声明词,一经修饰,底层会做相应的处理;至于做了什么,要跟blcok 存储域有关。blcok类型 有三种
栈上 NSConcreteStackBlock
堆上 NSConcreteMallocBlock
全局 NSConcreteGlobalBlock (存储在程序的数据区)
当使用的是栈上的block 时,你使用的又是局部变量,想截获变量改变其值,必须用__block 修饰,是将该变量变为一个结构体自动变量才能被修改,示例如下:
block_t=blk;
{
__block NSMutableArray *array=[[NSMutableArray alloc] init];
blk=^(NSObject obj){
[array add object:obj];
}
blk([[NSObject alloc] init]);
}
或者这么写:
block_t=blk;
{
NSMutableArray *array=[[NSMutableArray alloc] init];
blk=[^(NSObject obj){
[array add object:obj];
} copy]
blk([[NSObject alloc] init]);
}
这两种写法 都是 将变量array 从 栈copy 到 堆上,第一种是array 变量通过 block 修饰符生成一个forwarding指针,指向其拷贝到堆上的生成的自动变量结构体;第二种是把block 拷贝到堆上截获的变量也会被拷贝到堆上,这样当运行完大括号的代码时,array 也不会被释放,知道block 运行完成被释放时,array 才会跟着被释放释放是由系统调用dispose完成。
关于对象类型自动变量捕获,使用方法一还是方法二有个小总结:
1)block作为函数返回值返回时
2)将block赋值给类的附有__strong 修饰符的id 类型或block类型成员变量时
3)向方法名中含有usingBlock 的cocoa框架方法或GCD 的api 传递block 时;
除了以上几种情况外,其他都建议使用方法二。
由于以上变量都是 strong 类型变量,如果是weak 类型变量呢?类似如下:
block_t=blk;
{
__block NSMutableArray __weak *array=[[NSMutableArray alloc] init];
blk=^(NSObject obj){
[array add object:obj];
}
blk([[NSObject alloc] init]);
}
经验证,运行完代码,在作用域外array 会被释放,array 不会有任何改变。
3、blcok循环引用
block 的循环引用是我们平时写代码经常要注意的问题,循环引用的原因即为 block 强持有该 自动变量,自动变量又强持有 blcok,运行完后释放时你等待我释放,我等待你释放,由此造成死循环;类似的有多线程中的死锁问题,二等待一运行完一等待二运行完导致两者都在等待执行卡死在那儿。如下图
循环引用示意图避免循环引用的方式主要是两种:
1)使用__weak 或 __unsafe_unretained修饰自动变量使得blcok 弱持有 自动变量,当block 执行完后,blcok 和对象都能被释放
2)使用__block 修饰自动变量 , 但是 必须要调用 一次 block ,才能使得 block变量对 自动变量的持有释放,不然还是会有循环引用
4、blcok 拷贝/释放
有一个小点,显示调用copy 和release 的情况,是在非ARC 情况下即需要我们主动去调用release 方法 将copy 到堆里的block 释放掉,这里我不做详细描述,有兴趣的可以自己去度娘问问;
block 的使用博大精深,需要更进一步的探寻它的使用场景,期待与你一起深入学习研究。。。
参考:
1、<<Object-C 高级编程iOS 与OSX 多线程和内存管理>>