k语法后,即使改写block中使用的自动变量的值也不会影响block执行时自动变量的值。 尝试改写block中捕获的自动变量,将会是编译错误。我更喜欢把这个理解为:block捕获的自动变量都将转化为const类型。不可修改了 解决办法是将自动变量添加修饰符 __block;那么如果截获的自动变量是OC对象呢
^{[array addObject:obj];};
这么写是没有问题的,因为array是一个指针,我们并没有改变指针的值。这个也可以解释下面的问题
const char text[] = "hello";
^{ printf("%c\n",text[2]);};
这样会编译错误。为何?这是因为捕获自动变量的方法并没有实现C语言数组类型。可以通过指针代替:const char *text= "hello"; 那么这个block的对象结构是什么样呢,请看下面:
__main_block_impl_0{
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
struct __main_block_desc_0 *Desc;
int val;
} 这个val是如何传递到block结构体中的呢?
int main(){
struct __main_block_impl_0 *blk = &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA,val);
}注意函数调用最后一个参数,即val参数。 那么函数调用的代码页转化为下面这样了.这里的cself跟C++的this和OC的self一样。
static struct __main_block_func_0(struct __main_block_impl_0 *__cself)
{
printf("val=%d\n",__cself-val);
}所以,block捕获变量更像是:函数按值传递。
__block说明符
前面讲过block所在函数中的,捕获自动变量。但是不能修改它,不然就是编译错误。但是可以改变全局变量、静态变量、全局静态变量。 其实这两个特点不难理解:第一、为何不让修改变量:这个是编译器决定的。理论上当然可以修改变量了,只不过block捕获的是自动变量的副本,名字一样。为了不给开发者迷惑,干脆不让赋值。道理有点像:函数参数,要用指针,不然传递的是副本。 第二、可以修改静态变量的值。静态变量属于类的,不是某一个变量。所以block内部不用调用cself指针。所以block可以调用。 解决block不能保存值这一问题的另外一个办法是使用__block修饰符。
__block int val = 10;
void (^blk)(void) = ^{val = 1;}; 该源码转化后如下:
struct __block_byref_val_0{
void *__isa;
__block_byref_val_0 *__forwarding;
int _flags;
int __size;
int val;
} __main_block_impl_0中自然多了__block_byreg_val_0的一个字段。注意:__block_byref_val_0结构体中有自身的指针对象,难道要 _block int val = 10;这一行代码,转化成了下面的结构体 __block)byref_val_0 val = {0,&val,0,sizeof(__block_byref_val_0),10};//自己持有自己的指针。 它竟然变成了结构体了。之所以为啥要生成一个结构体,后面在详细讲讲。反正不能直接保存val的指针,因为val是栈上的,保存栈变量的指针很危险。
block存储区域
这就需要引入三个名词: ● _NSConcretStackBlock ● _NSConcretGlobalBlock
● _NSConcretMallocBlock
正如它们名字说的那样,说明了block的三种存储方式:栈、全局、堆。__main_block_impl_0结构体中的isa就是这个值。 【要点1】如果是定义在函数外面的block是global的,另外如果函数内部的block但是,没有捕获任何自动变量,那么它也是全局的。比如下面这样的代码:
typedef int (^blk_t)(int);
for(...){
blk_t blk = ^(int count) {return count;};
} 虽然,这个block在循环内,但是blk的地址总是不变的。说明这个block在全局段。 【要点2】一种情况在非ARC下是无法编译的: typedef int(^blk_t)(int); blk_t func(int rate){ return ^(int count){return rate*count;} } 这是因为:block捕获了栈上的rate自动变量,此时rate已经变成了一个结构体,而block中拥有这个结构体的指针。即如果返回block的话就是返回局部变量的指针。而这一点恰是编译器已经断定了。在ARC下没有这个问题,是因为ARC使用了autorelease了。 【要点3】有时候我们需要调用block 的copy函数,将block拷贝到堆上。看下面的代码:
-(id) getBlockArray{
int val =10;
return [[NSArray alloc]initWithObjects:
^{NSLog(@"blk0:%d",val);},
^{NSLog(@"blk1:%d",val);},nil];
}
id obj = getBlockArray();
typedef void (^blk_t)(void);
blk_t blk = (blk_t){obj objectAtIndex:0};
blk(); 这段代码在最后一行blk()会异常,因为数组中的block是栈上的。因为val是栈上的。解决办法就是调用copy方法。 【要点4】不管block配置在何处,用copy方法复制都不会引起任何问题。在ARC环境下,如果不确定是否要copy block尽管copy即可。ARC会打扫战场。 注意:在栈上调用copy那么复制到堆上,在全局block调用copy什么也不做,在堆上调用block 引用计数增加 【注意】本人用Xcode 5.1.1 iOS sdk 7.1 编译发现:并非《Objective-C》高级编程这本书中描述的那样 int val肯定是在栈上的,我保存了val的地址,看看block调用前后是否变化。输出一致说明是栈上,不一致说明是堆上。
typedef int (^blkt1)(void) ;
-(void) stackOrHeap{
__block int val =10;
int *valPtr = &val;//使用int的指针,来检测block到底在栈上,还是堆上
blkt1 s= ^{
NSLog(@"val_block = %d",++val);
return val;};
s();
NSLog(@"valPointer = %d",*valPtr);
} 在ARC下――block捕获了自动变量,那么block就被会直接生成到堆上了。 val_block = 11 valPointer = 10 在非ARC下――block捕获了自动变量,该block还是在栈上