block是c语言的扩充功能。用一句话概括block的功能:带有局部变量的匿名函数。
block语法
^ 返回值类型 参数列表 表达式 如:^int (int count) { return count + 1}
返回值类型可以省略。返回值类型省略的时候,如果reture有返回值,就用该返回值类型,如果没有return,返回值类型就是void
^ 参数列表 表达式 如:^ (int count) {return count + 1}
如果不需要番薯,参数列表也可以省略
^ 表达式 如:^ {return 1;}
block类型的变量
将block赋值给block变量。
c语言函数指针 返回值类型 (指针名) (返回值类型) 如:int (funcptr) (int)
block变量类型 返回值类型 (^block变量名) (返回值类型) 如:int (^blockname) (int)
int (^blk) (int) = ^int (int count){
return count + 1;
}
int (^blk1) (int) = blk;
int (^blk2) (int);
blk2 = blk1;
使用typedef定义block类型
typedef int(^blk_t) (int);
void func(blk_t blk)
blk_t func(){
}
当然,也可以使用block指针
type int (^blk_t) (int);
blk_t blk = ^int (int count) {return count + 1};
blk_t blkprt = &blk;
(blkprt)(10);
block保存上下文
block会保存调用时外部变量的值。
比如
int val = 10;
void ^(blk) (void) = ^{printf(val);}
val = 20;
blk();
打印结果 10
在block中改变上下文的值会报错的
如
int val = 10;
void ^(blk) (void) = ^{val = 20}
val = 20;
编译报错
使用__block说明符修饰变量,可以在block内部改变外部变量的值
__block int val = 10;
void ^(blk) (void) = ^{val = 20}
blk();
print(val)
打印结果 20
自动保存的oc对象类型的变量,使用不会报错,赋值会报错
id array = [NSMutableArray alloc]init];
void (^blk) (void) = ^void (void) {
id obj = [NSObject new];
[array addObject:obj];
}
不会报错
id array = [NSMutableArray alloc]init];
void (^blk) (void) = ^void (void) {
array = [NSMutable alloc]init];
}
报错
需要加上__block
__block id array = [NSMutableArray alloc]init];
void (^blk) (void) = ^void (void) {
array = [NSMutable alloc]init];
}
不报错
block没有实现C语言数组的自动保存
const char text[] = "hello"
void (^blk) (void) = ^ {
print(text[2]);
}
报错
需要使用指针
const char * text = "hello"
void (^blk) (void) = ^ {
print(text[2]);
}
不报错
总结:
1.block会自动保存使用的外部局部变量。但是不会保存使用的C语言数组。
2.block不能给使用的外部局部变量赋值。如果要赋值(1.使用全局变量、静态变量、全局静态变量 2.使用__block)
block实现
oc中的block可以类比oc中的类。
声明一个block,就相当于声明一个类。包含了函数指针,指向元block结构体的指针isa。
image.png
block的自动保存变量
block中使用的外部变量被当成一个成员变量,并使用外部变量的值初始化成员变量,添加到生成的block结构体中。只有block中使用的成员变量才会被添加到结构体中。
这样,在block中是不能改变外部变量的。
block中使用全局静态变量、全局变量和局部静态变量,不会在block结构体中创建新变量,直接使用的原始变量。
我认为,block会保存局部变量,是因为block在调用的使用,已经超过了局部变量的声明周期,所以要做一份克隆。
__block修饰的变量,会变成结构体(a),然后block创建的结构体中会引用这个结构体(a)。
总结:block中要改变外部变量
1.使用全局变量、静态变量、全局静态变量
2.使用__block
block存储域
Block也是OC对象。
Block按照存储区域的不同,分为以下类
NSConcreteStackBlock 在栈上分配内存
NSConcreteBlobalBlock 在数据区分配内存
NSConcreteMallocBlock 在堆中分配内存
image.png
大部分情况下,block都在栈上分配内存,对应的类是NSConcreteStackBlock。
当在声明全局变量的地方使用block,或者block中不使用外部的局部变量的时候。block存放在数据区。
Block作为返回值的时候,编译器会自动克隆该block并放置到堆上面。
但是某些情况下,编译器不会自动复制block,需要手动复制。想方法或函数的参数中传递block时,一般需要复制一下。
- (id) getBlockArray
{
int val = 10;
return [[NSArray alloc] initWithObjects:
^{NSLog(@"block1:%@",val)},
^{NSLog(@"block2:%@",val)},nil];
}
在从该方法获取block的时候,就会报错。
需要copy一下再放入
- (id) getBlockArray
{
int val = 10;
return [[NSArray alloc] initWithObjects:
[^{NSLog(@"block1:%@",val)} copy],
[^{NSLog(@"block2:%@",val)} copy],nil];
}
不同的block调用copy有什么效果呢?
NSConcreteStackBlock 从栈复制到堆中
NSConcreteGlobalBlock 什么都不做
NSConcreteMallocBlock 引用记数增加
对Block调用copy,没有什么问题。多次调用也没什么问题。
总结:往函数中传递block的时候,最好copy一下。
例子
#import "ZiZhongBlockTest.h"
void (^globalblk)(void) = ^{NSLog(@"globalblk");};
@implementation ZiZhongBlockTest
- (void)testBlockWithoutParam:(void (^)(void))block {
NSLog(@"testBlockWithoutParam:%@",[block class]); //__NSGlobalBlock__
block();
}
- (void)testGlobalBlock {
NSLog(@"testGlobalBlock:%@",[globalblk class]); //__NSGlobalBlock__
globalblk();
}
- (void)testStackBlock:(void (^)(void))block {
NSLog(@"testStackBlock:%@",[block class]); //__NSStackBlock__
block();
}
- (id)getStackBlock {
int val = 10;
//栈上面分配的block会被销毁,导致错误
return [[NSArray alloc]initWithObjects:^{NSLog(@"stackBlk1,%d",val);}, ^{NSLog(@"stackBlk1,%d",val);}, nil];
// 改成copy
}
@end
调用
ZiZhongBlockTest * blockTest = [ZiZhongBlockTest new];
NSInteger i = 0;
[blockTest testBlockWithoutParam:^{
}];
[blockTest testGlobalBlock];
[blockTest testStackBlock:^{
NSLog(@"%ld",(long)i); // i为局部变量时,block在栈上分配。i为全局变量或静态变量时,在数据区分配。
}];
id blkArray = [blockTest getStackBlock];
typedef void (^myStackblk)(void);
myStackblk blk = (myStackblk)[blkArray objectAtIndex:0];
blk(); //报错
总结:
1.定义静态变量的地方创建block,会保存在代码区
2.block不使用外部变量,或使用全局变量、全局静态变量、局部静态变量时,会保存在代码区
3.block使用局部变量和局部静态变量,会保存在栈中
4.往数组、函数中添加block,最好copy一下。
__block变量存储域
block从栈上被复制到堆上时,如果block使用了__block变量,__block变量也会受到影响。
__block变量存储区域 | block从栈上复制到堆上时的影响
栈 | __block变量从栈上复制到对上,并被block持有
堆 | 不变
之前我们知道,使用__block修饰变量之后,会创建一个结构体,然后结构体中__forwarding指针指向了真正的变量。
__block复制到堆上之后,原来栈上__block结构体的__forwarding指针会指向堆上的__block结构体。因此复制之后,栈上的__block变量和堆上的__block变量指向的都是同一个结构体。
block中持有局部变量对象,当block被复制到堆中时,block内部会持有局部变量对象的强引用并增加局部变量对象的引用记数,在堆上的block被废弃时,会自动调用dispose函数废弃局部变量对象。
比如
image.png image.png
总结:
1.block会自动保存使用的外部局部变量。但是不会保存使用的C语言数组。
2.block不能给使用的外部局部变量赋值。如果要赋值(1.使用全局变量、静态变量、全局静态变量 2.使用__block)
3.block也是一种类,有三种类型,分别是NSConcreteStackBlock(栈上分配内存)、NSConcreteBlobalBlock(数据区分配内存)、NSConcreteMallocBlock(堆中分配内存)。
4.block使用外部局部变量,会在栈上分配内存。
5.block不使用外部变量,或者使用静态变量、全局变量、全局静态变量,会在数据区中分配内存
6.创建的静态block,会在数据区分配内存
7.使用__block修饰变量,会创建一个结构体,然后结构体中保存变量的值。通知结构体有一个__forawrd指针,指向结构体真正的地址。调用__block变量a时,其实调用的是a->__forawrd->val
8.不同block使用copy,效果也不一样
NSConcreteStackBlock 从栈复制到堆中
NSConcreteGlobalBlock 什么都不做
NSConcreteMallocBlock 引用记数增加
对Block调用copy,没有什么问题。多次调用也没什么问题。
9.block从栈中复制到堆中时,如果block中使用了__block变量,__block变量也会在堆中复制一份。同时栈中__block变量的__forwarding会指向堆中__block变量。保证栈中操作__block变量和堆中效果一样。
10.栈上block被复制到堆中的时机(1.调用block的copy方法、2.block作为返回值返回时 3.block赋值给strong修饰的id类型的类 3.在方法名中含有usingBlock的cocoa框架或Grand Center Dispatch的API中传递block时)
11.除了上面提到的三种情况,其他情况最好调用block的copy方法。
网友评论