在ios,blocks是对象,它封装了一段代码,这段代码可以在任何时候执行。
Blocks可以作为函数参数或者函数的返回值,而其本身又可以带输入参数或返回值。
它和传统的函数指针很类似,但是有区别:
blocks是inline的,并且它对局部变量是只读的。
Block是C级别的语法和运行时特性。
Block比较类似C函数,但是Block比之C函数,其灵活性体现在栈内存、堆内存的引用,我们甚至可以将一个Block作为参数传给其他的函数或者Block。
Blocks的定义:
int (^myBlock) (int a,int b) = ^(int a,int b){
return a+b;
};
定义了一个名为myBlock的blocks对象,它带有两个int参数,返回int。等式右边就是blocks的具体实现。
为了性能,默认Block都是分配在stack上面的,所以它的作用区域就是当前函数。
只要没对Block进行copy操作,它一直存在stack里面。一旦进行了block的copy操作,
block就会放在堆里面。
以下是block基础的简单示例:
void (^BoolBlock)(BOOL);
typedef void (^BoolBlock)(BOOL);
//一个只接受一个BOOL参数,没有返回值的block
typedef int (^IntBlock)(void);
//一个没有参数,返回int的block
typedef BoolBlock (^HugeBlock)(IntBlock);
//看看,这个HugeBlock的参数和返回值都是block
#import <Foundation/Foundation.h>
int main(int argc,constchar * argv[])
{
@autoreleasepool {
// 简单示例
//block的使用范围
int i =1024;
void (^blk)(void) = ^ {
printf("%d\n", i);
};
blk();
//block的简单定义
int (^blockPlus)(int a,int b) = ^(int a,int b){
return a+b;
};
int C = blockPlus(3,5);
NSLog(@"%d",C);
//block改变外部变量
int outA =8;
int (^myPtr)(int) = ^(int a){return outA + a;};//block里面可以读取同一类型的outA的值
outA = 5; //在调用myPtr之前改变outA的值
int result = myPtr(3); // result的值仍然是11,并不是8
NSLog(@"result=%d", result);
//为什么result的值仍然是11?而不是8呢?事实上,myPtr在其主体中用到的outA这个变量值的时候做了一个copy的动作,把outA的值copy下来,存出道另外一个位置。所以,之后outA即使换成了新的值,对于myPtr里面copy的值是没有影响的。
//需要注意的是,这里copy的值是变量的值,如果它是一个记忆体的位置(地址),换句话说,就是这个变量是个指针的话,它的值是可以在block里被改变的。
//block 的目的是为了支持并行编程,对于普通的 local 变量,我们就不能在 block 里面随意修改(原因很简单,block 可以被多个线程并行运行,会有问题的),而且如果你在 block 中修改普通的 local 变量,编译器也会报错。那么该如何修改外部变量呢?有两种办法,第一种是可以修改 static 全局变量;第二种是可以修改用新关键字 __block 修饰的变量。
//block改变外部变量
__block int B =4;
int (^blockPlusC)(int a,int b) = ^(int a,int b){
B = blockPlus(B,b);
NSLog(@"B = %d",B);
B = blockPlus(a,b);
return B;
};
B = 7;
NSLog(@"B = %d",B);
int A = blockPlusC(4,5);
NSLog(@"A = %d~~B = %d",A,B);
//block中对static中的值的影响
staticint outA1 =8;
int (^myPtr1)(int) = ^(int a){return outA + a;};
outA1 = 5;
int result1 = myPtr1(3); //result的值是8,因为outA是static类型的变量
NSLog(@"result1 = %d", result1);
//block中对static中的值的修改
staticint outA22 =8;
int (^myPtr22)(int) = ^(int a){
outA22 = 5;
return outA22 + a;
};
int result22 = myPtr22(3); //result的值是8,因为outA是static类型的变量
NSLog(@"result22=%d", result22);
//对象的存取
NSString * str =@"BBB";
NSString * (^blockString)() = ^(){
NSString * b =@"aaa";
return b;
};
str = blockString();
NSLog(@"str3 = %@",str);
//对象的改变
//需要注意的是,这里copy的值是变量的值,如果它是一个记忆体的位置(地址),换句话说,就是这个变量是个指针的话,它的值是可以在block里被改变的。
如下例子:
NSMutableArray *mutableArray = [NSMutableArrayarrayWithObjects:@"one",@"two",@"three", nil];
int resultww = ^(int a){
[mutableArray addObject:@"four"];
return a*a;
}(5);
NSLog(@"test array :%@ %d", mutableArray,resultww);
//原本mutableArray的值是{@"one",@"two",@"three"},在block里面被更改mutableArray后,就变成{@"one", @"two"}了。
//但是这种变更是在block截获OC对象变量的值的基础上的。
//在blocks中,block表达式截获所实用的自动变量的值,即保存该自动变量的瞬间值。
//即使改变了block中使用的自动变量的值也不会影响block执行时的自动变量的值。
//但是,如果想在block语法中将值赋给在block语法外声明的自动变量,需要在该自动变量上附上 __block.
//如果将值赋给block中截获的自动变量(非对象),就会产生编译错误!
//但如果截获的是Objective-C对象,block调用变更该对象的方法是不会有错误的,而向截获的变量赋值则会产生编译错误。
//即如上面最近的代码所示,该block中所截获的变量值为NSMutableArray类的对象。如果用C描述,即是截获的NSMutableArray类对象用的结构体实例指针。虽然赋值给截获的自动变量array的操作会产生编译错误,但使用截获的值却不会有任何问题。
// 生命周期和内存管理
//
// 因为block也是继承自NSObject,所以其生命周期和内存的管理也就非常之重要。
// block一开始都是被放到堆里,换句话说其生命周期随着method或function结束就会被回收,和一般变量的生命周期一样。
// 关于内存管理请遵循这几点
// 1. block pointer的实现会在method或function結束后就会被清理
// 2. 如果要保存block pointer要用- copy指令,这样block pointer就會被放到堆里
// 2.1 block 实现用到的block variable也会被移到到heap而有新的内存地址,且一并更新有用到这个block variable的block都指到新的位置
// 2.2 一般的variable值会被copy
// 2.3 如果主体里用到的variable是object的话,此object会被retain, block release時也会被release
// 2.4 __block variable 里用到的object是不会被retain的
}
return 0;
}
// 2.4 __block variable 里用到的object是不会被retain
稍做补充:
1,Block是Objective C语言中的对象 。
2,Block对象与一般的类实例对象有所不同,一个主要的区别就是分配的位置不同,block默认在栈上分配,一般类的实例对象在堆上分配。
retain是对一个在堆中分配内存的对象的引用计数做了增加,执行release操作的时候检查计数是否为1,如果是则释放堆中内存。
而对于在栈上分配的block对象,这一点显然有所不同,如果方法调用返回,栈帧上的数据自然会作废处理,不像堆上内存,需要单独release,就算NSArray对block对象本身做了retain也无济于事。