Block详解

正文
一、Block简介
我们可以把Block当做Objective-C的匿名函数。Block允许开发者在两个对象之间将任意的语句当做数据进行传递,往往这要比引用定义在别处的函数直观。另外,block的实现具有封闭性(closure),而又能够很容易获取上下文的相关状态信息。
Block的创建
实际上,block使用了与函数相同的机制:可以像声明函数一样,来声明一个bock变量;可以利用定义一个函数的方法来定义一个block;也可以将block当做一个函数来调用。
// main.m 
#import  
int main(int argc, const char * argv[]) { 
    @autoreleasepool { 
        // Declare the block variable 
        double (^distanceFromRateAndTime)(double rate, double time); 

        // Create and assign the block 
        distanceFromRateAndTime = ^double(double rate, double time) { 
            return rate * time; 
        }; 
        // Call the block 
        double dx = distanceFromRateAndTime(35, 1.5); 

        NSLog(@"A car driving 35 mph will travel " 
              @"%.2f miles in 1.5 hours.", dx); 
    } 
    return 0; 

在上面的代码中,利用插入符(^)将distanceFromRateAndTime变量标记为一个block。就像声明函数一样,需要包含返回值的类型,以及参数的类型,这样编译器才能安全的进行强制类型转换。插入符(^)跟指针(例如 int *aPointer)前面的星号(*)类似——只是在声明的时候需要使用,之后用法跟普通的变量一样。
block的定义本质上跟函数一样——只不过不需要函数名。block以签名字符串开始:^double(double rate, double time)标示返回一个double,以及接收两个同样为double的参数(如果不需要返回值,可以忽略掉)。在签名后面是一个大括弧({}),在这个括弧里面可以编写任意的语句代码,这跟普通的函数一样。
当把block赋值给distanceFromRateAndTime后,我们就可以像调用函数一样调用这个变量了。
不带参数的Block
如果block不需要任何的参数,那么可以忽略掉参数列表。另外,在定义block的时候,返回值的类型也是可选的,所以这样情况下,block可以简写为^ { … }:
double (^randomPercent)(void) = ^ { 
    return (double)arc4random() / 4294967295; 
}; 
NSLog(@"Gas tank is %.1f%% full", 
      randomPercent() * 100); 
在上面的代码中,利用内置的arc4random()方法返回一个32位的整型随机数——为了获得0-1之间的一个值,通过除以arc4random()方法能够获取到的最大值(4294967295)。 
到现在为止,block看起来可能有点像利用一种复杂的方式来定义一个方法。事实上,block是被设计为闭包的(closure)——这就提供了一种新的、令人兴奋的编程方式。
Block的闭包性(closure)
在block内部,可以像普通函数一样访问数据:局部变量、传递给block的参数,全局变量/函数。并且由于block具有闭包性,所以还能访问非局部变量(non-local variable)。非局部变量定义在block之外,但是在block内部有它的作用域。例如,getFullCarName可以使用定义在block前面的make变量:
NSString *make = @"Honda"; 
NSString *(^getFullCarName)(NSString *) = ^(NSString *model) { 
    return [make stringByAppendingFormat:@" %@", model]; 
}; 
NSLog(@"%@", getFullCarName(@"Accord"));    // Honda Accord 
非局部变量会以const变量被拷贝并存储到block中,也就是说block对其是只读的。如果尝试在block内部给make变量赋值,会抛出编译器错误。
以const拷贝的方式访问非局部变量,意味着block实际上并不是真正的访问了非局部变量——只不过在block中创建了非局部变量的一个快照。当定义block时,无论非局部变量的值是什么,都将被冻结,并且block会一直使用这个值,即使在之后的代码中修改了非局部变量的值。下面通过代码来看看,在创建好block之后,修改make变量的值,会发生什么:
NSString *make = @"Honda"; 
NSString *(^getFullCarName)(NSString *) = ^(NSString *model) { 
    return [make stringByAppendingFormat:@" %@", model]; 
}; 
NSLog(@"%@", getFullCarName(@"Accord"));    // Honda Accord   
// Try changing the non-local variable (it won't change the block) 
make = @"Porsche"; 
NSLog(@"%@", getFullCarName(@"911 Turbo")); // Honda 911 Turbo 
block的闭包性为block与上下文交互的时候带来极大的便利性,当block需要额外的数据时,可以避免使用参数——只需要简单的使用非局部变量即可。
修改非局部变量
冻结中的非局部变量是一个常量值,这也是一种默认的安全行为——因为这可以防止在block中的代码对非局部变量做了意外的修改。那么如果我们希望在block中对非局部变量值进行修改要如何做呢——用__block存储修饰符(storage modifier)来声明非局部变量:
__block NSString *make = @"Honda"; 
这将告诉block对非局部变量做引用处理,在block外部make变量和内部的make变量创建一个直接的链接(direct link)。现在就可以在block外部修改make,然后反应到block内部,反过来,也是一样。
通过引用的方式访问非局部变量
这跟普通函数中的静态局部变量(static local variable)类似,用__block修饰符声明的变量可以记录着block多次调用的结果。例如下面的代码创建了一个block,在block中对i进行累加。
__block int i = 0; 
int (^count)(void) = ^ { 
    i += 1; 
    return i; 
}; 
NSLog(@"%d", count());    // 1 
NSLog(@"%d", count());    // 2 
NSLog(@"%d", count());    // 3 
Block作为函数的参数
把block存储在变量中有时候非常有用,比如将其用作函数的参数。这可以解决类似函数指针能解决的问题,不过我们也可以定义内联的block,这样代码更加易读。
例如下面Car interface中声明了一个方法,该方法用来计算汽车的里程数。这里并没有强制要求调用者给该方法传递一个常量速度,相反可以改方法接收一个block——该block根据具体的时间来定义汽车的速度。
// Car.h 
#import  
@interface Car : NSObject 
@property double odometer; 
- (void)driveForDuration:(double)duration 
       withVariableSpeed:(double (^)(double time))speedFunction 
                   steps:(int)numSteps; 
@end 
上面代码中block的数据类型是double (^)(double time),也就是说block的调用者需要传递一个double类型的参数,并且该block的返回值为double类型。注意:上面代码中的语法基本与本文开头介绍的block变量声明相同,只不过没有变量名字。
在函数的实现里面可以通过speedFunction来调用block。下面的示例通过算法计算出汽车行驶的大约距离。其中steps参数是由调用者确定的一个准确值。
// Car.m 
#import "Car.h" 

@implementation Car 

@synthesize odometer = _odometer; 

- (void)driveForDuration:(double)duration 
       withVariableSpeed:(double (^)(double time))speedFunction 
                   steps:(int)numSteps { 
    double dt = duration / numSteps; 
    for (int i=1; i<=numSteps; i++) { 
        _odometer += speedFunction(i*dt) * dt; 
    } 

@end 
在下面的代码中,有一个main函数,在main函数中block定义在另一个函数的调用过程中。虽然理解其中的语法需要话几秒钟时间,不过这比起另外声明一个函数,再定义withVariableSpeed参数要更加直观。
// main.m 
#import  
#import "Car.h" 
int main(int argc, const char * argv[]) { 
    @autoreleasepool { 
        Car *theCar = [[Car alloc] init]; 
        // Drive for awhile with constant speed of 5.0 m/s 
        [theCar driveForDuration:10.0 
               withVariableSpeed:^(double time) { 
                           return 5.0; 
                       } steps:100]; 
        NSLog(@"The car has now driven %.2f meters", theCar.odometer);   
        // Start accelerating at a rate of 1.0 m/s^2 
        [theCar driveForDuration:10.0 
               withVariableSpeed:^(double time) { 
                           return time + 5.0; 
                       } steps:100]; 
        NSLog(@"The car has now driven %.2f meters", theCar.odometer); 
    } 
    return 0; 


上面利用一个简单的示例演示了block的通用性。在iOS的SDK中有许多API都利用了block的其它一些功能。NSArray的sortedArrayUsingComparator:方法可以使用一个block对元素进行排序,而UIView的animateWithDuration:animations:方法使用了一个block来定义动画的最终状态。此外,block在并发编程中具有强大的作用。

定义Block类型
由于block数据类型的语法会很快把函数的声明搞得难以阅读,所以经常使用typedef对block的签名(signature)做处理。例如,下面的代码创建了一个叫做SpeedFunction的新类型,这样我们就可以对withVariableSpeed参数使用一个更加有语义的数据类型。
// Car.h 
#import  
// Define a new type for the block 
typedef double (^SpeedFunction)(double); 
@interface Car : NSObject 
@property double odometer; 
- (void)driveForDuration:(double)duration 
       withVariableSpeed:(SpeedFunction)speedFunction 
                   steps:(int)numSteps; 
@end 
许多标准的Objective-C框架也使用了这样的技巧,例如NSComparator。
总结

Block不仅提供了C函数同样的功能,而且block看起来更加直观。block可以定义为内联(inline),这样在函数内部调用的时候就非常方便,由于block具有闭包性(closure),所以block可以很容易获得上下文信息,而又不会对这些数据产生负面影响。

二、block 注意事项
1block 在实现时就会对它引用到的它所在方法中定义的栈变量进行一次只读拷贝,然后在 block 块内使用该只读拷贝。
如下代码:
- (void)testAccessVariable
{
NSInteger outsideVariable = 10;
    //__block NSInteger outsideVariable = 10;
    NSMutableArray * outsideArray = [[NSMutableArray alloc] init];
    void (^blockObject)(void) = ^(void){
        NSInteger insideVariable = 20;
        KSLog(@"  > member variable = %d", self.memberVariable);
        KSLog(@"  > outside variable = %d", outsideVariable);
        KSLog(@"  > inside variable = %d", insideVariable);
        [outsideArray addObject:@"AddedInsideBlock"];
    };
    outsideVariable = 30;
    self.memberVariable = 30;
    blockObject();
    KSLog(@"  > %d items in outsideArray", [outsideArray count]);
}
输出结果为:
  > member variable = 30
  > outside variable = 10
  > inside variable = 20
  > 1 items in outsideArray
注意到没?outside 变量的输出值为10,虽然outside变量在定义 block 之后在定义 block 所在的方法 testAccessVariable 中被修改为 20 了。这里的规则就是:blockObject 在实现时会对 outside 变量进行只读拷贝,在 block 块内使用该只读拷贝。因此这里输出的是拷贝时的变量值 10。如果,我们想要让 blockObject 修改或同步使用 outside 变量就需要用 __block 来修饰 outside 变量。
__block NSInteger outsideVariable = 10;
注意:
a),在上面的 block 中,我们往 outsideArray 数组中添加了值,但并未修改 outsideArray 自身,这是允许的,因为拷贝的是 outsideArray 自身。 
b),对于 static 变量,全局变量,在 block 中是有读写权限的,因为在 block 的内部实现中,拷贝的是指向这些变量的指针。
c) __block 变量的内部实现要复杂许多,__block 变量其实是一个结构体对象,拷贝的是指向该结构体对象的指针。
2,非内联(inline block 不能直接访问 self,只能通过将 self 当作参数传递到 block 中才能使用,并且此时的 self 只能通过 setter  getter 方法访问其属性,不能使用句点式方法。但内联 block 不受此限制。
block 内存管理分析
block 其实也是一个 NSObject 对象,并且在大多数情况下,block 是分配在栈上面的,只有当 block 被定义为全局变量或 block 块中没有引用任何 automatic 变量时,block 才分配在全局数据段上。 __block 变量也是分配在栈上面的。


在 ARC 下,编译器会自动检测为我们处理了 block 的大部分内存管理,但当将 block 当作方法参数时候,编译器不会自动检测,需要我们手动拷贝该 block 对象。
在 ARC 下,对 block 变量进行 copy 始终是安全的,无论它是在栈上,还是全局数据段,还是已经拷贝到堆上。对栈上的 block 进行 copy 是将它拷贝到堆上;对全局数据段中的 block 进行 copy 不会有任何作用;对堆上的 block 进行 copy 只是增加它的引用记数。
如果栈上的 block 中引用了__block 类型的变量,在将该 block 拷贝到堆上时也会将 __block 变量拷贝到堆上如果该 __block 变量在堆上还没有对应的拷贝的话,否则就增加堆上对应的拷贝的引用记数。
 

block就是一个代码块,但是它的神奇之处在于在内联(inline)执行的时候(这和C++很像)还可以 传递参数。 同时 block本身也可以被作为参数在方法和函数间传递, 这就给予了block无限的可能。


block如何申明(对比于c语言中的函数申明)

图片:block如何申明.png 
block <wbr>(三) <wbr>和函数指针有什么区别

三、和函数指针的区别

第一个区别,函数指针是对一个函数地址的引用,这个函数在编译的时候就已经确定了。而block是一个函数对象,是在程序运行过程中产生的。在一个作用域中生成的 block 对象分配在栈 (stack) 上, 和其他所有分配在栈上的对象一样,离开这个作用域,就不存在了。
Block允许开发者在两个对象之间将任意的语句当做数据进行传递,往往这要比引用定义在别处的函数直观。

Block实体形式如下:
^(传入参数列){行为主体};
Block实体开头是“^”,接着是由小括号所包起来的参数列(比如 int a, int b, int c),行为主体由大括号包起来,专有名字叫做block literal。行为主体可以用return回传值,类型会被compiler自动辨别。如果没有参数列要写成: ^(void)。
例如下面的一个例子:
^(int a){return a*a;};  
这是代表Block会回传输入值的平方值(int a 就是参数列, return a*a; 就是行为主体)。记得行为主体里最后要加“;”,因为是叙述,而整个{}最后也要加“;”,因为Block是物件实体。
在ios开发中,blocks是对象,它封装了一段代码,这段代码可以在任何时候执行。Blocks可以作为函数参数或者函数的返回值,而其本身又可以带输入参数或返回值。它和传统的函数指针很类似,但是有区别:blocks是inline的,并且它对局部变量是只读的。
Blocks的定义:
     int (^myBlock) (int a,int b) = ^(int a,int b){  
       return a+b;  
   };  
定义了一个名为myBlock的blocks对象,它带有两个int参数,返回int。等式右边就是blocks的具体实现,是不是有点像方法的定义?
Blocks可以访问局部变量,但是不能修改。比如下面的代码就会报编译错 
int num = 0;  
    //使用block   
    int (^myBlock) (int a,int b) = ^(int a,int b){  
        num = a+b;  
        return num;  
    };  
如果要修改就要加关键字:__block (注意,是两个下划线"_")
__block int num = 0;  
    //使用block   
    int (^myBlock) (int a,int b) = ^(int a,int b){  
        num = a+b;  
        return num;  
    };  
作为函数的参数,blocks某种意义上替代了回调函数或者delegate。当函数调用了,假设某个事件触发,这时blocks里的内容就会运行。这样有利于代码的整合和阅读,你不需要到处去实现委托方法了。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值