一、什么是Block

  • Block是将函数及其执行上下文封装的对象。
  • __block_impl结构体内部中有isa,说明Block本质是OC对象。

二、Block变量截获

  1. 局部变量(对象)截获是值截获。比如:
NSInteger num = 1;
    NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n)  {
        return  n * num;
    };
    num = 2;
    NSLog(@"%zd",block(3));

这里输出的是3,而不是2,原因就是对局部变量num的截获是值截获。
同样,在block里修改num也是无效的,甚至编译器会报错。

  1. 局部静态变量截获是指针截获。比如:
static NSInteger num = 1;
    NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n)  {
        return  n * num;
    };
    num = 2;
    NSLog(@"%zd",block(3));

这里输出为6,意味着这里的num=2的修改是有效的,即是指针截获。
同样,在block里修改变量num也是有效的。

  1. 全局变量、静态全局变量截获:不截获,直接取值。

我们用clang编译看下结果:

static NSInteger num3 = 3;
NSInteger num4 = 4;

- (void)blockTest
{
    NSInteger num = 1;
    static NSInteger num2 = 2;
    __block NSInteger num5 = 5;

    void(^block)(void) = ^ {
        NSLog(@"%zd",num);//局部变量
        NSLog(@"%zd",num2);//静态变量
        NSLog(@"%zd",num3);//全局变量
        NSLog(@"%zd",num4);//全局静态变量
        NSLog(@"%zd",num5);//__block修饰变量
    };
    block();
}

编译后:

block ios 不执行 ios __block_局部变量

  • impl.isa = &_NSConcreteStackBlock; 即说明该 block 是栈block。
  • 局部变量被编译成值形式,静态变量被编译成指针形式,全局变量并未截获。而__block修饰的变量也是以指针形式截获的,并且生成了一个新的结构体。

block ios 不执行 ios __block_block_02

  • 该对象有个属性num5,即我们用__block修饰的变量,这里__forwarding是指向自身的(栈block)。
  • 一般情况下,如果我们要对block截获的局部变量进行赋值操作需添加__block修饰符,而对全局变量、静态变量是不需要添加__block修饰符的。另外block里去访问self或成员变量都会去截获self。

三、Block的几种形式

  • 分为全局 Block(_NSConcreteGlobalBlock)、栈 Block(_NSConcreteStackBlock)、堆 Block(_NSConcreteMallocBlock)三种形式。
  • 其中栈Block存储在栈(stack)区,堆Block存储在堆(heap)区,全局Block存储在已初始化数据(.data)区。
  1. 不使用外部变量的Block是全局Block。
NSLog(@"%@",[^{
        NSLog(@"globalBlock");
    } class]);

输出:

__NSGlobalBlock__
  1. 使用外部变量并且未进行copy操作的Block是栈Block。
NSInteger num = 1;
    NSLog(@"%@",[^{
        NSLog(@"statcBlock:%zd",num);
    } class]);

输出:

__NSStackBlock__
  1. 对栈Block进行copy操作就是堆Block,而对全局Block进行copy还是全局Block。
  • 比如堆1中的全局进行copy操作,即赋值:
void(^globalBlock)(void) = ^{
        NSLog(@"globalBlock");
    };
    NSLog(@"%@",[globalBlock class]);

输出:

__NSGlobalBlock__

仍然是全局

  • 对2中的栈Block进行copy操作,即赋值:
void(^mallocBlock)(void) = ^{
        NSLog(@"statcBlock:%zd",num);
    };
    NSLog(@"%@",mallocBlock);

输出:

__NSMallocBlock__

即如果对栈Block进行copy,将会copy到堆上;对堆Block进行copy,将会增加引用计数;对全局Block进行copy,因为是已经初始化的,所以什么也不做。

另外,__block变量在copy时,由于 __forwarding的存在,栈上的__forwarding指针会指向堆上的__forwarding变量,而堆上的__forwarding指针指向其自身,所以,如果是对__block的修改,其实是在修改堆上__block的变量。

即__forwarding指针存在的意义是,无论在任何内存位置,都可以顺利的访问同一个__block变量。

另外由于block捕获的__block修饰的变量会去持有变量,那么如果用__block去修饰self,self持有block,并且block内部使用__block修饰的self时,就会造成循环引用,即self持有block,block持有__block变量,__block变量持有self,造成内存泄漏。

比如

__block typeof (self) weakSelf = self;
    _testBlock = ^{
        NSLog(@"%@",weakSelf);
    };
    _testBlock();

如果要解决这种循环引用,可以主动的断开__block变量对self的持有,即在block内部执行完weakSelf后,将其置为nil。但这种方法有个问题,如果block一直不被调用,那么循环引用将一直存在。所以,我们最好使用__weak来修饰self。