一、Block整体概念
看了《OC高级编程》这本书,对block有了更深一层的认识。之前只是会用,很多原理性的东西都不了解。书中通过分析源码来解释block,第一次看的时候,我一头雾水。后来项目中遇到了问题,又几次回来细看,终于有些体会。
书中提到:IOS中,block就是一个对象。
你只要理解这点,很多东西都可以关联上来。我试着证明这点:

结果为:

上面有两个地址:存储地址和内容地址(我自己定义的,哈哈)
只要是对象,都会有这两个地址。
对于普通类型,比如上面的 int a,它只有存储地址,就是存放a内容的地址。
对于对象类型,它的存储地址存放的不是对象的内容,而是存放了对象内容的地址(内容地址),内容地址下才是对象的具体内容。多了一层。
所以上面的,a、arr和block的存储地址都比较接近,属于同一个区域的,都是局部变量,所以都是在栈上分配的空间。但arr是对象,它真正是存储在堆区的,栈区只是存了它堆区的地址。对比arr和block的内容地址,也比较接近,都是堆区,所以block真正的内容也是在堆区,那么它也是和对象类似的结构。
Block就是一个对象,那么它的block实现方法便是对象的成员函数。
二、对局部变量的捕获
Block是对象,上面提到了成员函数,那我的成员函数有时候需要访问block外部的局部变量,要么通过参数传进来,要么把局部变量变为它的成员变量。Block是通过第二种方式,把外部的局部变量,直接拷贝为它的成员变量。如下:

结果为:

可见,外部的a和block内部的a已经不是同一个变量了。对比block的地址,可见block内部的a已经是堆区的内容了。
对于外部的局部变量,block做的只是照搬工作,你是int a,那么内部成员变量也是int a,没有任何变动。
对于局部的对象类型(NSArray等)也是如此,只是对象拷贝的是内容指针(因为存储下装的内容就是一个堆区的地址)。那么,外部和block内部的成员变量,操作的都是同一个堆区内容。
那么对于C的类型呢?我试验下:

外部定义: chartext[] ="hello";
Block内部访问,会报错!
为什么呢?其实很简单,照搬机制,内部就是char text[],这个是不能直接赋值。按这个道理,改成指针就对了:char *text = "hello";
三、对静态变量和全局变量的捕获
静态变量和全局变量,不是照搬了,改成了指针的方法,这样就能保证所有的block都操作同一个内容。
比如:static int a = 10;
变成:int *a;

结果为:

可见,操作的是同一个地址!!存在于data区。
四、对__block修饰的变量的理解
__block定义的变量,在block里面修改了,并且可以影响外部的值,这就不能是简单的复制了。
按照书中所说,__block变量最后也会变成一个对象,对象里面有个_forwarding指针,默认指向本身。当发生栈区到堆区的拷贝(堆区只有一份),指针便指向堆区的结构体,这样就能保证所有情况都可以顺利地访问同一个__block变量。简单说,就是通过操作结构体内的一个指针来控制最后指向的位置,操作不同的值。
1、没拷贝到堆区的情况

结果为:

因为block内部没有使用,没有发生栈到堆的拷贝。所有__block变量还是在栈区。
2、拷贝到堆区的情况

结果为:

A已经指向了堆区。
所以:
1、__block定义的变量,如果在block内部使用了(无需调用,定义了就改变了),那么后面的变量都会变为堆区的那个。
2、只要发生了拷贝到堆区,后面所有的block都是使用同一个堆区变量,所以的block使用同一个__block变量。
网友评论