接下来让咱们经过源码来看一看Block的本质 git
这里咱们经过一个clang的编译命令clang -rewrite-objc xxx.m来看一下源码的实现github
咱们的那段代码经过编译器编写后,首先第一行I表明的是一个实例方法后面的是对象和方法名,传了两个参数一个是self,一个是选择器因子编程
而后咱们方法中的第一行代码在编译后没有发生改变,咱们着重看一下Block方法编译后的改变bash
首先咱们能够看到__BlockOneObj__testMethod_block_impl_0这样一个结构体,在这个结构体中传递了几个参数,第一个参数(void*)__BlockOneObj__testMethod_block_func_0咱们经过名字能够知道这是一个无类型的函数指针,第二个参数&__BlockOneObj__testMethod_block_desc_0_DATA是一个Block相关描述的结构体而后取地址符,第三个参数muIntNum就是咱们定义的局部变量。最后取这个结构体地址强制转换赋值给咱们定义的这个Block数据结构
而后咱们来看看__BlockOneObj__testMethod_block_impl_0这个结构体中有什么具体操做,以下图多线程
其中第一个结构体里面又是什么数据结构呢,请看下图 函数
在咱们上面介绍的结构体下面还有一个函数,具体解释请看下图 ui
Block的调用其实就是函数的调用,从源码中咱们能够看出来spa
首先先对这个Block进行一个强制类型转换(__block_impl *)Block线程
以后又取出它之中的成员变量FuncPtr(函数指针),找到咱们上面解析的结构体和函数,在其中拿到对应的函数调用,而后把其中的参数传递进去,一个参数是咱们这个Block自己,一个是咱们传递的2,而后就回去调用__BlockOneObj__testMethod_block_func_0函数,最终进行调用
首先咱们先来看一段代码
- (void)testMethod {
int muIntNum = 6;
int(^Block)(int) = ^int(int num){
return num *muIntNum;
};
muIntNum = 4;
Block(2);
}
复制代码
这段代码执行完Block(2)返回的值是多少呢?-------答案是12 接下来咱们看一下为何是12以及Block截获变量的本质是什么
对于基本数据类型的局部变量截获其值
对于对象类型的局部变量连同其全部权修饰符一块儿截获
对于局部静态变量是以指针形式去截获
对于全局变量和静态全局变量不截获
下面直接上代码
#import "BlockTwoObj.h"
//全局变量
int global_var = 4;
//全局静态变量
static int static_global_var = 5;
@implementation BlockTwoObj
- (void)testMethodTwo {
//基本数据类型的局部变量
int var = 1;
//对象类型的的局部变量
__unsafe_unretained id unsafe_obj = nil;
__strong id strong_obj = nil;
//局部静态变量
static int static_var = 3;
void(^Block)(void) = ^{
NSLog(@"基本数据类型局部变量:%d", var);
NSLog(@"对象类型局部变量(__unsafe_unretained修饰):%@", unsafe_obj);
NSLog(@"对象类型局部变量(__strong修饰):%@", strong_obj);
NSLog(@"局部静态变量:%d", static_var);
NSLog(@"全局变量:%d", global_var);
NSLog(@"全局静态变量:%d", static_global_var);
};
Block();
}
@end
复制代码
接下来咱们经过clang命令clang -rewrite-objc -fobjc-arc xxx.m来看一下源码
咱们在什么状况下使用__block修饰符呢? 通常状况下,对被截获变量进行赋值操做须要添加__block修饰符,这里须要注意的是赋值不等因而使用,切记!!!
例如在下面的代码中是否须要__block修饰符来修饰
NSMutableArray *muArr = [[NSMutableArray alloc] init];
void(^Block)(void) = ^{
//这里只是作了添加操做,并不是赋值,因此不须要用__block进行修饰
[muArr addObject:@"111"];
};
Block();
复制代码
那么在下面的代码段当中呢?
__block NSMutableArray *muArrOther = nil;
void(^BlockOther)(void) = ^{
//这里作了赋值操做,因此须要用__block进行修饰,不然会出现编译报错
muArrOther = [NSMutableArray array];
};
BlockOther();
复制代码
对变量进行赋值时
须要__block修饰符修饰的是局部变量(包括基本数据类型和对象类型)
不须要__block修饰符修饰的是静态局部变量、全局变量和静态全局变量,由于对于全局变量和静态全局变量不涉及到变量的截获,而对于静态局部变量呢,是经过使用指针来操做对应的变量的,因此也不须要修饰
下面请看一段代码,仍是咱们上面的那个例子
- (void)testMethod {
__block int muIntNum = 6;
int(^Block)(int) = ^int(int num){
return num *muIntNum;
};
muIntNum = 4;
Block(2);
}
复制代码
此时Block返回的是8,这里是为何呢,咱们只是用了__block来修饰
请看下面的流程图
首先__block int muIntNum会被转化成第一个这样一个结构体,其中具备isa指针,咱们也能够理解成一个对象
从这个角度来看muIntNum通过编译后就会变成一个对象,经过__forwarding指针去找到对应的对象,而后进行赋值
刚才咱们看到的代码段是在栈上,在__block变量中有一个__forwarding指针,而这个指针指向的是本身,这里要注意的是前提是在栈上,若是在堆上,这个__forwarding指针指向的就不是本身了,在下面会讲到
因此在栈上咱们修改这个变量的值,就会经过__forwarding指针找到本身本省去修改这个变量的值
那么这里有一个问题就是咱们在栈上这个__forwarding指向的是本身到底有什么用呢?咱们彻底能够经过访问成员变量来修改,为何还须要这个指针呢,请继续往下看
Block有三种类型
_NSConcreteGlobalBlock 全局Block
_NSConcreteStackBlock 栈Block
_NSConcreteMallocBlock 堆Block
Block的Copy操做
当咱们栈上的Block经过copy在堆上产生一个同样的Block,有相同的Block和__block变量,当变量做用于结束后,栈上的Block对象就会被销毁,而堆上的block依旧存在,全部若是栈上Block不用copy拷贝到堆上,在做用于销毁后会由于找不到Block对象而崩溃
固然咱们在这里有一个问题,假如说在MRC环境下,若是在栈上进行了copy操做,会不会产生内存泄漏,答案是确定的,至关于一个对象alloc出来,可是并无对应的relese操做同样
Objective - C 高级编程:iOS与OS X多线程和内存管理