Block对象
Block对象是一组指令,可以像调用函数一样调用Block对象。
函数和方法最大的差别是:方法只能由某个对象或类来执行。这也是面向过程编程和面向对象编程的概念性差别。
block对象集成了 面向过程编程和面向对象编程的特点。
- Block对象也是一段可执行的代码,可以接收实参并返回单个数值。
- 并且,block对象也是对象。
声明block对象
因为block对象也是对象,所以可以用指针变量指向某个block对象。
指向block对象的变量也有:变量名和类型两个属性。
类型:实参和返回值 int(^adder)(int a, int b);(声明一个名为adder的block变量)
定义block对象
^int(int a, int b){
return x+y;
}; //这种定义语法称为block literal
执行block对象
int(^adder)(int , int ) = ^int(int a, int b){//可以忽略block对象实参的参数名,因为只有block对象自身的参数名才有效
return x+y;
};
int sum = adder(3, 4);
NSLog(@"%d", sum);
Block对象在方法(函数)内创建,(方法和函数都是在实现文件中独立定义的)。
使用Block对象
创建新的NSObject子类,命名为Executor,增加一个实例变量和两个方法:
//.h
@interface Executor:NSObject{
int (^equation) (int, int);
}
- (void)setEquation:(int^(int, int))block;
- (int)computeWithValue:(int)value1 andValue:(int)value2;
@end
//.m
@implementation Executor
- (void)setEquation:(int(^)(int, int))block{
equation = block;
}
- (int)computeWithValue:(int)value1 andValue:(int)value2{
//如果某个block变量没有指向任何一个block对象,执行该变量会导致应用崩溃
if(!equation){return 0;}
//向block对象传入两个值,并返回结果
return equation(value1, value2);}
@end
//.其他使用block的
#import "Executor.h"
Executor *executor =[[Executor alloc] init];
[executor setEquation:adder];
int m = [executor computeWithValue:2 andValue5];
当实参的类型是Block对象时,一样可以不通过变量,直接传入Block对象
[executor setEquation:^int (int a, int b){return a+ b;}];
和其他实例变量一样,也可以将block对象声明为类的属性,以替代存方法,因为会在实现文件中合成该属性,所以不需要在为equation属性声明相应的实例变量
//.h
@interface Executor:NSObject
@property(nonatomic, copy)int (^equation) (int, int);
- (int)computeWithValue:(int)value1 andValue:(int)value2;
@end
捕获变量capture
block对象也能使用传入的实参并声明局部变量
block对象也可以使用其封闭作用域enclosing scope内的所有变量,对声明了block变量的方法,该方法的作用域就是这个block对象的封闭作用域,因此这个block对象可以访问该方法的所有局部变量、传入该方法的实参以及传入当前对象的实例变量 。
当某个Block对象访问了一个该Block对象之前声明的变量时,可以称该 Block对象捕获了这个变量,因此在这段代码中,应用会将mutiplier的值拷贝至block对象的内存,当应用执行该block对象时,无论最初的mutiplier的值是否发生了变化,都会使用当时赋值时刻的值。一旦block对象捕获了某个变量,该变量之后的变化就不会对捕获后的变量产生任何影响(block内部也不能修改,除非使用__block进行修饰)。
要点: 一旦block对象捕获了某个值,那么无论怎么样执行该Block对象,执行多少次,被捕获的值也不会发生变化。但是对block对象的实参,每次调用block对象,都可以传入不同的值。
block对象可以捕获人意类型的变量,包括指向对象的指针,因此,Block对象中的代码可以通过封闭作用域中的指针,向某个对象发送消息。
每一个iOS应用都有一个主操作队列(main operation queue),因为可以用block对象来构成操作,所以也可以将block对象加入主操作队列,应用会在某个运行循环中执行该block对象,加入操作队列的Block不能有返回值,也不能有任何实参。
[executor setEquation:^int(int x, int y){
int sum = x + y;
return multiplier * (sum + y);];
//得到指向主操作队列的指针,然后为该队列添加一个Block对象
[[NSOperationQueue mainQueue] addOperationWithBlock:^void(void){
NSLog(@"%d", [executor computeWithValue: 2 andValue: 5]);
}];
通常情况下,应该针对主操作队列调用addOperationWithBlock:。对当前的运行循环,可能需要先完成视图的绘制工作并释放相应的对象,再执行某个Block对象。
但是这样会导致一个问题executor对象并没有被释放,因为Block对象会针对其作用域内所使用的对象,保留强引用类型的引用。因此Block对象向某个对象发消息,或者使用某个对象,都会导致应用保留该对象,直到释放相应的Block对象。(Block对象会像捕获普通变量那样捕获指向对象的指针变量,当编译器发现捕获的变量是指向某个对象的指针时,就会将相应的Block对象设置为该对象的拥有方)。
Block对象的常见用途
最常见的用途:Block对象实现回调
Block对象针对某个事件的一次性回调,而且该事件可以在将来某个时刻发生。通常会将这种用途的Block对象称为Completion Block 。
使用completion block时,不需要声明或定义某个方法,甚至不需要使用对象,只需要在事件发生前创建相应的Block对象就能设置回调。Block对象是一种不依赖于对象的回调。
iOS SDK中有很多API使用到了Block对象。NSArray会使用sortedArrayUsingComparator:的方法,当NSArray对象收到该消息时,会使用传入的Block对象来比较其包含的每个对象,然后返回一个新的排过序的NSArray对象:
NSArray*sorted = [array sortedArrayUsingComparator:
^NSComparisonResult(id obj1, id obj2){
if([obj1 value] > [obj2 value])
return NSOrderedAscending;
else if([obj1 value] < [obj2 value])
return NSOrderedDescending;
return NSOrderedSame;
}];
如果某个方法有类型为Block对象的实参,那么在调用该方法时,就必须传入与之匹配的Block对象。一方面类型不匹配,编译器报错。另一方面是因为某个方法在处理传入的Block对象时,会按预期的格式来调用相应的Block对象,并期望其返回指定类型的数值。可以将这种用途的Block对象看成某种“插件”。
Block还有很多用途,例如串联执行不同的代码(一个Block对象调用另一个Block对象,这个Block对象在调用另一个Block对象)。此外,Block对象还可以帮助应用充分利用多个CPU内核,同步执行多段代码。
深入学习:__block、简化语法、内存管理
^ returnType(...){}
这里的返回值不是必须的,编译器可以通过根据Block对象中的return语句,自动判断返回类型
int(^block)(void) = ^(void){return 10;}
NSString *(^block)(void) = ^(void){return [NSString stringWithString:@"Hey"];}
void(^block)(void) = ^{NSLog(@"Not gonna return anything...")};
其次,如果某个Block对象没有实参,可以在创建该对象时忽略参数列表:
int(^block)(void) = ^{return 10;}
但是在Block声明时,不能忽略返回类型和空的参数列表,必须写全。
int five = 5;
void(^block)(void) = ^{five = 6;//这行代码会产生错误};
此时需要使用__block关键字,将相应的变量声明为"可修改的",如果某个变量是__block修饰的,应用就会在一块特殊的内存空间中保存该变量:既不是当前的栈,也不是block对象自身的内存空间,这意味着可以有多个block对象修改同一个__block变量。
如果某个block对象捕获的是指向某个对象的指针,那么即使没有使用__block,也一样能修改该对象的属性(但是不能修改指针指向的对象),如果使用__block修饰了某个指针变量,就可以在Block对象中修改该指针所指向的对象。
NSString *string1 = @"abc";
__block NSString *string2 = @"egg";
void (^block)(void) = ^{
string1 = @"ABCD";//错误
string2 = @"ABCD";//正确✅}
应用会将新创建的Block对象保存在栈中,即使应用针对新创建的block对象保留了强引用类型的指针,一旦创建该对象的方法返回,新创建的Block对象一样会被释放。
所以要向Block对象发送copy消息,执行拷贝操作。拷贝某个block对象时,应用会在堆中创建该对象的备份。这样,即使应用释放了当前方法的栈,堆中的block对象也不会被释放。如果收到copy消息的block对象已经位于堆中的拷贝,就只会增加一个指向对象的强引用,并不会重复拷贝。
ARC之后,即使将某个Block对象赋给一个强引用特性的指针变量,编译器也能够对block对象执行更多的自动处理,但最好还是显式的调用copy方法。
http://www.opensource.apple.com
深入学习:多种回调机制的优缺点
所谓回调:是在某个事件发生前预先准备好一段代码,相应的事件一旦发生,系统就会执行这段代码。
其他可以实现回调机制还包括:委托,目标-动作对,通告。
回调机制两部分组成:注册过程。事件发生时需要执行的代码。
如果通过delegate、目标-动作对、通告实现,就需要“注册”一个指向某个对象的指针,以便能够在事件发生时,向该对象发送消息。此外,目标-动作对和通告都需要设置一个SEL变量,用于保存需要发送的消息。对委托,则需要预先定义委托协议,列出要发送的消息。对于这三种回调模式都需要使用独立的方法来实现回调代码。
如果两个对象关系紧密(例如视图控制器对象和其视图),就可以使用目标-动作对来实现回调。
如果某个对象会收到多个事件,而且需要由同一个对象来处理所有的事件,就可以使用委托来实现回调。
如果需要多个对象(或者两个无关的对象)处理同一件事件时,就可以使用通告机制:需要处理特定时间的对象(或多个对象)可以针对响应的事件将自己注册为观察器,而产生事件的对象可以向通告中心发布响应的通告。
使用Block对象实现回调可以和对象无关。block对象适用于只会发生一次的回调,或者回调处理的是很简单的任务(例如更新进度条)。
block对象适用于只会发生一次的回调的原因:一是Block对象会保留其所用到的对象,只要应用没有释放某个block对象,该对象所用到的对象也不会被释放。虽然可以在不需要使用某个block对象的时候释放这个对象,但可能会产生一个问题:当某个对象A拥有某个Block对象B,而Block对象B又用到了对象A时,就会产生retain循环问题,“一次性的”Block对象可以避免此问题。二是可以将注册某个Block对象的代码和Block对象自身的代码写在同一个代码段里,从而使代码更简洁。

浙公网安备 33010602011771号