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对象自身的代码写在同一个代码段里,从而使代码更简洁。

 

 

 

 

posted @ 2015-09-05 15:54  captivity  阅读(602)  评论(0)    收藏  举报