char *text ="nicee";
char again[] = "jacky";
NSArray *newdatas = @[@"first",@"second"];
NSLog(@"again=%s", again);
NSLog(@"texzt=%s", text);
printf("%c, %c\n", text[1], again[1]);
printf("%s, %s\n", text, again);
void (^testBlock)(void) = ^{
printf("%s\n", text);
// printf("%s\n", again);
NSLog(@"newdatas[1]=%@", newdatas[1]);
};
testBlock();
首先先看几道block相关的题目
这是一篇比较长的博文,前部分是block的测试题目,中间是block的语法、特性,block讲解block内部实现和block存储位置,请读者耐心阅读。具备block基础的同学,直接调转到block的实现
下面列出了五道题,看看能否答对两三个。主要涉及block栈上、还是堆上、怎么捕获变量。答案在博文最后一行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
//-----------第一道题:--------------
void
exampleA() {
char
a =
'A'
;
^{ printf(
"%c\n"
, a);};
}
A.始终能够正常运行 B.只有在使用ARC的情况下才能正常运行
C.不使用ARC才能正常运行 D.永远无法正常运行
//-----------第二道题:答案同第一题--------------
void
exampleB_addBlockToArray(NSMutableArray *array) {
char
b =
'B'
;
[array addObject:^{printf(
"%c\n"
, b);}];
}
void
exampleB() {
NSMutableArray *array = [NSMutableArray array];
exampleB_addBlockToArray(array);
void
(^block)() = [array objectAtIndex:
0
];
block();
}
//-----------第三道题:答案同第一题--------------
void
exampleC_addBlockToArray(NSMutableArray *array) {
[array addObject:^{printf(
"C\n"
);}];
}
void
exampleC() {
NSMutableArray *array = [NSMutableArray array];
exampleC_addBlockToArray(array);
void
(^block)() = [array objectAtIndex:
0
];
block();
}
//-----------第四道题:答案同第一题--------------
typedef
void
(^dBlock)();
dBlock exampleD_getBlock() {
char
d =
'D'
;
return
^{printf(
"%c\n"
, d);};
}
void
exampleD() {
exampleD_getBlock()();
}
//-----------第五道题:答案同第一题--------------
typedef
void
(^eBlock)();
eBlock exampleE_getBlock() {
char
e =
'E'
;
void
(^block)() = ^{printf(
"%c\n"
, e);};
return
block;
}
void
exampleE() {
eBlock block = exampleE_getBlock();
block();
}
|
注:以上题目摘自:CocoaChina论坛点击打开链接
block概要
什么是block
Blocks是C语言的扩充功能。可以用一句话来表示Blocks的扩充功能:带有自动变量(局部变量)的匿名函数。命名就是工作的本质,函数名、变量名、方法名、属性名、类名和框架名都必须具备。而能够编写不带名称的函数对程序员来说相当有吸引力。 例如:我们要进行一个URL的请求。那么请求结果以何种方式通知调用者呢?通常是经过代理(delegate)但是,写delegate本身就是成本,我们需要写类、方法等等。 这时候,我们就用到了block。block提供了类似由C++和OC类生成实例或对象来保持变量值的方法。像这样使用block可以不声明C++和OC类,也没有使用静态变量、静态全局变量或全局变量,仅用编写C语言函数的 源码 量即可使用带有自动变量值的匿名函数。 其他语言中也有block概念。block的实现
block的语法看上去好像很特别,但实际上是作为极为普通的C语言代码来处理的。这里我们借住clang编译器的能力:具有转化为我们可读源代码的能力。 控制台命令是: clang -rewrite-objc 源代码文件名。
1
2
3
4
5
|
int
main(){
void
(^blk)(
void
) = ^{printf(
"block\n"
);};
blk();
return
0
;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
struct __block_impl{
void
*isa;
int
Flags;
int
Reserved;
void
*FuncPtr;
};
static
struct __main_block_desc_0{
unsigned
long
reserved;
unsigned
long
Block_size
}__main_block_desc_0_DATA = {
0
, sizeof(struct __main_block_impl_0)};
struct __main_block_impl_0{
struct __block_impl impl;
struct __main_block_desc_0 *Desc;
}
static
struct __main_block_func_0(struct __main_block_impl_0 *__cself)
{
printf(
"block\n"
);
}
int
main(){
struct __main_block_impl_0 *blk = &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);
(*blk->impl.FuncPtr)(blk);
}
|
__main_block_impl_0:block变量。
__main_block_func_0:虽然,block叫,匿名函数。但是,这个函数还是被编译器起了个名字。
__main_block_desc_0:block的描述,注意,他有一个实例__main_block_desc_0_DATA 上述命名是有规则的:main是block所在函数的名字,后缀0则是这个函数中的第0个block。由于上面是C++的代码,可以将__main_block_impl_0的结构体总结一下,得到如下形式:
1
2
3
4
5
6
7
|
__main_block_impl_0{
void
*isa;
int
Flags;
int
Reserved;
void
*FuncPtr;
struct __main_block_desc_0 *Desc;
}
|
截获自动变量值
1
2
3
4
|
int
val =
10
;
void
(^blk)(
void
) = ^{printf(
"val=%d\n"
,val);};
val =
2
;
blk();
|
上面这段代码,输出值是:val = 10.而不是2.block截获自动变量的瞬时值。因为block保存了自动变量的值,所以在执行block语法后,即使改写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的对象结构是什么样呢,请看下面:
1
2
3
4
5
6
7
8
|
__main_block_impl_0{
void
*isa;
int
Flags;
int
Reserved;
void
*FuncPtr;
struct __main_block_desc_0 *Desc;
int
val;
}
|
1
2
3
|
int
main(){
struct __main_block_impl_0 *blk = &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA,val);
}
|
1
2
3
4
|
static
struct __main_block_func_0(struct __main_block_impl_0 *__cself)
{
printf(
"val=%d\n"
,__cself-val);
}
|
__block说明符
前面讲过block所在函数中的,捕获自动变量。但是不能修改它,不然就是编译错误。但是可以改变全局变量、静态变量、全局静态变量。 其实这两个特点不难理解:第一、为何不让修改变量:这个是编译器决定的。理论上当然可以修改变量了,只不过block捕获的是自动变量的副本,名字一样。为了不给开发者迷惑,干脆不让赋值。道理有点像:函数参数,要用指针,不然传递的是副本。 第二、可以修改静态变量的值。静态变量属于类的,不是某一个变量。所以block内部不用调用cself指针。所以block可以调用。 解决block不能保存值这一问题的另外一个办法是使用__block修饰符。
1
2
|
__block
int
val =
10
;
void
(^blk)(
void
) = ^{val =
1
;};
|
1
2
3
4
5
6
7
|
struct __block_byref_val_0{
void
*__isa;
__block_byref_val_0 *__forwarding;
int
_flags;
int
__size;
int
val;
}
|
block存储区域
这就需要引入三个名词: ● _NSConcretStackBlock ● _NSConcretGlobalBlock● _NSConcretMallocBlock
正如它们名字说的那样,说明了block的三种存储方式:栈、全局、堆。__main_block_impl_0结构体中的isa就是这个值。 【要点1】如果是定义在函数外面的block是global的,另外如果函数内部的block但是,没有捕获任何自动变量,那么它也是全局的。比如下面这样的代码:
1
2
3
4
|
typedef
int
(^blk_t)(
int
);
for
(...){
blk_t blk = ^(
int
count) {
return
count;};
}
|
1
2
3
4
5
6
7
8
9
10
11
|
-(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();
|
1
2
3
4
5
6
7
8
9
10
|
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);
}
|
调用copy之后的结果呢:
1
2
3
4
5
6
7
8
9
10
|
-(
void
) stackOrHeap{
__block
int
val =
10
;
int
*valPtr = &val;
//使用int的指针,来检测block到底在栈上,还是堆上
blkt1 s= ^{
NSLog(@
"val_block = %d"
,++val);
return
val;};
blkt1 h = [s copy];
h();
NSLog(@
"valPointer = %d"
,*valPtr);
}
|
----------------在ARC下>>>>>>>>>>>无效果。 val_block = 11 valPointer = 10
----------------在非ARC下>>>>>>>>>确实复制到堆上了。 val_block = 11 valPointer = 10用这个表格来表示 /*当block捕获了自动变量时候 ------------------------------------------------------------------ | where block stay | ARC | 非ARC | -------------------------------------------------------------------
| copy | heap | heap | ------------------------------------------------------------------ | no copy | heap | stack | ------------------------------------------------------------------
*/
__block变量存储区域
当block被复制到堆上时,他所捕获的对象、变量也全部复制到堆上。 回忆一下block捕获自动变量的时候,自动变量将编程一个结构体,结构体中有一个字段叫__forwarding,用于指向自动这个结构体。那么有了这个__forwarding指针,无论是栈上的block还是被拷贝到堆上,那么都会正确的访问自动变量的值。截获对象
block会持有捕获的对象。编译器为了区分自动变量和对象,有一个类型来区分。
1
2
3
4
5
6
|
static
void
__main_block_copy_0(struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src){
_Block_objct_assign(&dst->val,src->val,BLOCK_FIELD_IS_BYREF);
}
static
void
__main_block_dispose_0(struct __main_block_impl_0 *src){
_block_object_dispose(src->val,BLOCK_FIELD_IS_BYREF);
}
|
__block修饰符可用于任何类型的自动变量
【__block循环引用】
根据上面讲的内容,block在持有对象的时候,对象如果持有block,会造成循环引用。解决办法有两种:
1. 使用__weak修饰符。id __weak obj = obj_
2. 使用__block修饰符。__block id tmp = self;然后在block中tmp = nil;这样就打破循环了。这个办法需要记得将tmp=nil。不推荐!
文章开头block测试题答案:ABABB
1
2
3
4
|
int
val =
10
;
void
(^blk)(
void
) = ^{printf(
"val=%d\n"
,val);};
val =
2
;
blk();
|
上面这段代码,输出值是:val = 10.而不是2.block截获自动变量的瞬时值。因为block保存了自动变量的值,所以在执行block语法后,即使改写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";