block
来源:互联网 发布:流星网络电视官网 编辑:程序博客网 时间:2024/06/03 23:45
1.Block定义
可以用一句话来表示Block:带有自动变量(局部变量)的匿名函数。
在iOS中使用“^”来声明一个Block。Block的内容是包含在“{}”中的,并且和C语言一样用“;”来表示语句的结束,标准语法如下所示:
^ 返回值类型 参数列表 表达式
我们通常使用如下形式将Block赋值给Block类型变量,示例代码如下:
1 int multiplier = 7;2 3 int (^myBlock)(int) = ^(int num){ 4 return multiplier * num; 5 };6 7 NSLog(@"%d",myBlock(3));
采用这种方式在函数参数或返回值中使用Block类型变量时,记述方式极为复杂。这时,我们可以使用typedef来解决该问题。
示例1:没有使用typedef
1 - (void)loadDataFromUrl:(void(^)(NSString *))retData2 {3 }
示例2:使用typedef
1 typedef void(^RetDataHandler)(NSString *);2 - (void)loadDataFromUrl:(RetDataHandler)retData3 {4 5 }
从上面的代码可以看到,使用typedef声明之后,在方法中传递block参数时,更容易理解。
Block的强大之处是:在声明它的范围里,所有变量都可以为其所捕获。下面我们来看看自动变量。
2.自动变量
从上面Block语法的介绍中,我们可以理解“带有自动变量(局部变量)的匿名函数”中的匿名函数。那么“带有自动变量(局部变量)”是什么呢?这个在Block中表现为“截获自动变量值”。示例如下:
1 int iCode = 10; 2 NSString *strName = @"Tom"; 3 4 void (^myBlock)(void) = ^{ 5 // 结果:My name is Tom,my code is 10 6 NSLog(@"My name is %@,my code is %d",strName,iCode); 7 }; 8 9 iCode = 20;10 strName = @"Jim";11 12 myBlock();13 // 结果:My name is Jim,my code is 2014 NSLog(@"My name is %@,my code is %d",strName,iCode);
从代码中可以看到,Block表达式截获所使用的自动变量iCode和strName的值,即保存该自动变量的瞬间值。因为Block表达式保存了自动变量的值,所以在执行Block语法后,即使改写Block中所用的自动变量的值也不会影响Block执行时自动变量的值,这就是自动变量值的截获。
如果我们想在Block中修改截获的自动变量值,会有什么结果?咱们做个尝试:
从上面可以看到,该源代码会产生编译错误。若想在Block语法的表达式中将值赋给在Block语法外声明的自动变量,需要在该自动变量上附加__block说明符,示例如下:
1 __block NSString *strName = @"Tom"; 2 3 void (^myBlock)(void) = ^{ 4 strName = @"Sky"; 5 }; 6 strName = @"Jim"; 7 8 myBlock(); 9 // 结果:My name is Sky10 NSLog(@"My name is %@",strName);
需要说明的是,对于截获的自动变量,调用变更该对象的方法是没有问题的:即赋值给截获的自动变量会产生编译错误,但使用截获的自动变量的值却不会有任何问题。
3.如何在代码中创建Block?
3.1不带参数和返回值的block
1 - (void)testBlockOne2 {3 void (^myBlock)(void) = ^{4 NSLog(@"Hello Block One");5 };6 7 NSLog(@"%@",myBlock);8 myBlock();9 }
3.2带参数的block
1 - (void)testBlockTwo{2 void (^myBlock)(NSString *) = ^(NSString *str){3 NSLog(@"Hello Block %@",str);4 };5 6 NSLog(@"%@",myBlock);7 myBlock(@"ligf");8 }
3.3带参数和返回值的block
1 - (void)testBlockThree{ 2 int (^myBlock)(NSString *,int) = ^(NSString *str,int code){ 3 NSLog(@"Hello Block %@,code is %d", str, code); 4 return 1; 5 }; 6 7 NSLog(@"%@",myBlock); 8 int iRet = myBlock(@"ligf",3); 9 NSLog(@"%d",iRet);10 }
4.block实现页面传值
4.1先用传统的Delegate来进行示例
WebServicesHelper类:
1 @class WebServicesHelper; 2 3 @protocol WebServicesHelperDelegate <NSObject> 4 5 - (void)networkFecherSuccess:(WebServicesHelper *)networkFetcher 6 didFinishWithData:(NSString *)data; 7 - (void)networkFecherFailed:(WebServicesHelper *)networkFetcher 8 error:(NSError *)error; 9 10 @end11 12 @interface WebServicesHelper : NSObject13 14 @property (nonatomic, retain) NSURL *url;15 @property (nonatomic, assign) id<WebServicesHelperDelegate> delegate;16 17 - (id)initWithUrl:(NSURL *)url;18 - (void)startDownload;19 20 @end21 - (id)initWithUrl:(NSURL *)url22 {23 self = [super init];24 25 if (self)26 {27 self.url = url;28 }29 30 return self;31 }32 33 - (void)startDownload34 {35 NSError *error = nil;36 NSString *str = [NSString stringWithContentsOfURL:self.url encoding:NSUTF8StringEncoding error:&error];37 38 if (error)39 {40 [_delegate networkFecherFailed:self error:error];41 }42 else43 {44 [_delegate networkFecherSuccess:self didFinishWithData:str];45 }46 }
DownloadByDelegate类:
1 #import "WebServicesHelper.h" 2 3 @interface DownloadByDelegate : NSObject<WebServicesHelperDelegate> 4 5 - (void)fetchUrlData; 6 7 @end 8 @implementation DownloadByDelegate 9 10 - (void)fetchUrlData11 {12 NSURL *url = [[NSURL alloc] initWithString:@"http://www.baidu.com"];13 WebServicesHelper *webServicesHelper = [[WebServicesHelper alloc] initWithUrl:url];14 webServicesHelper.delegate = self;15 [webServicesHelper startDownload];16 }17 18 - (void)networkFecherSuccess:(WebServicesHelper *)networkFetcher19 didFinishWithData:(NSString *)data20 {21 NSLog(@"%@",data);22 }23 24 - (void)networkFecherFailed:(WebServicesHelper *)networkFetcher25 error:(NSError *)error;26 {27 NSLog(@"%@",error);28 }29 30 @end
调用:
1 DownloadByDelegate *downloadByDelegate = [[DownloadByDelegate alloc] init];2 3 [downloadByDelegate fetchUrlData];
4.2再看看用Block的实现
DownloadByBlock类:
1 typedef void(^NetworkFetcherCompletionHandler) (NSString *data,NSError *error); 2 3 @interface DownloadByBlock : NSObject 4 5 - (id)initWithUrl:(NSURL *)url; 6 - (void)startWithCompletionHandler:(NetworkFetcherCompletionHandler)completion; 7 8 @end 9 @implementation DownloadByBlock10 {11 NSURL *_url;12 }13 14 - (id)initWithUrl:(NSURL *)url15 {16 self = [super self];17 18 if (self)19 {20 _url = url;21 }22 23 return self;24 }25 26 - (void)startWithCompletionHandler:(NetworkFetcherCompletionHandler)completion27 {28 NSError *error;29 NSString *str = [NSString stringWithContentsOfURL:_url encoding:NSUTF8StringEncoding error:&error];30 completion(str,error);31 }32 33 @end
调用:
1 DownloadByBlock *downloadByBlock = [[DownloadByBlock alloc] initWithUrl:[NSURL URLWithString:@"http://www.baidu.com"]];2 [downloadByBlock startWithCompletionHandler:^ (NSString *data,NSError *error){3 NSLog(@"%@",data);4 }];
从上面的代码可以明显看到,使用Block方式,代码的可读性更高,使用也更加的方便。
【小练习】:在我们的NC产品中,以前基本上都是使用Protocol方式实现的,重构之后的代码,大部分我已经用Block方式实现了,大家可以拿着以前的代码,然后采用Block方式试试,如果遇到问题,可以用重构之后的代码比照一下。
5.Block存储域
先看一个示例:
1 int (^myBlockOne)(int,int) = ^ (int a, int b) { 2 return a + b; 3 }; 4 // myBlockOne = <__NSGlobalBlock__: 0x100002080> 5 NSLog(@"myBlockOne = %@", myBlockOne); 6 7 int base = 100; 8 int (^myBlockTwo)(int,int) = ^ (int a, int b) { 9 return base + a + b;10 };11 // myBlockTwo = <__NSStackBlock__: 0x7fff5fbff8a0>12 NSLog(@"myBlockTwo = %@", myBlockTwo);13 14 int (^myBlockThree)(int,int) = [[myBlockTwo copy] autorelease];15 // myBlockThree = <__NSMallocBlock__: 0x1001047d0>16 NSLog(@"myBlockThree = %@", myBlockThree);
从上面的代码可以看到,Block在内存中的位置可以分为三种类型:__NSGlobalBlock__,__NSStackBlock__, __NSMallocBlock__。
- __NSGlobalBlock__:与全局变量一样,该类对象存储在程序的数据区域(.data区)中;
- __NSStackBlock__:从名称中可以看到含有“Stack”,即该类对象存储在栈上;位于栈上的Block对象,函数返回后Block将无效,变成野指针;
- __NSMallocBlock__:该类对象由malloc函数分配的内存块(堆)中。
其中在全局区域和堆里面存储的对象是相对安全的,但是在栈区里面的变量是危险的,有可能造成程序的崩溃,因此在iOS中如果使用block的成员变量或者属性时,需要将其copy到堆内存中。
上面的例子中,myBlockOne和myBlockTwo的区别在于:myBlockOne没有使用Block以外的任何外部变量,Block不需要建立局部变量值的快照,这使myBlockOne与函数没有任何区别。myBlockTwo与myBlockOne唯一不同是的使用了局部变量base,在定义(注意是定义,不是运行)myBlockTwo时,局部变量base当前值被截获到栈上,作为常量供Block使用。执行下面代码,结果是103,而不是203。
1 int base = 100;2 int (^myBlockTwo)(int,int) = ^ (int a, int b) {3 return base + a + b;4 };5 base = 200;6 NSLog(@“%d",myBlockTwo(1,2));
我们再看一段代码,大家思考一下这段代码有没有问题?
1 int base = 100; 2 void(^myBlock)(); 3 4 if (YES) 5 { 6 myBlock = ^{ 7 NSLog(@"This is ture,%d",base); 8 }; 9 }10 else11 {12 myBlock = ^{13 NSLog(@"This is false,%d",base);14 };15 }16 17 myBlock();
表面上看,和我们以前给变量赋值的语句没什么太大的差异,那么是不是没有问题呢?答案是NO。在定义这个块的时候,其所占的内存区域是分配在栈中的,块只在定义它的那个范围内有效,也就是说这个块只在对应的if或else语句范围内有效。当离开了这个范围之后,编译器有可能把分配给块的内存覆写掉。这样运行的时候,若编译器未覆写待执行的块,则程序照常运行;若覆写,则程序崩溃。
5.1Block的copy、retain、release
和OC中的对象的copy、retain、release不同,Block对象:
- Block_copy与copy等效,Block_release与release等效;
- 对Block不管是retain、copy、release都不会改变引用计数retainCount,retainCount始终是1;
- NSGlobalBlock:retain、copy、release操作都无效,因为全局块绝不可能为系统所回收。这种块实际上相当于单例;
- NSStackBlock:retain、release操作无效,必须注意的是,NSStackBlock在函数返回后,Block内存将被回收。即使retain也没用。容易犯的错误是[[mutableAarry addObject:stackBlock],在函数出栈后,从mutableAarry中取到的stackBlock已经被回收,变成了野指针。正确的做法是先将stackBlock copy到堆上,copy之后生成新的NSMallocBlock类型对象,然后返回: [[myBlock copy] autorelease]。
1 typedef NSString * (^retBlock)(void); 2 3 - (void)test 4 { 5 NSMutableArray *arr = [NSMutableArray array]; 6 7 retBlock block = [[self stackBlock] copy]; 8 9 for (int i = 0; i < 5; i++)10 {11 [arr addObject:block];12 }13 }14 15 - (retBlock)stackBlock16 {17 int ret = 10;18 19 NSString * (^myBlock)(void) = ^{20 return [NSString stringWithFormat:@"Hello,%d",ret];21 };22 23 return [[myBlock copy] autorelease];24 }
- NSMallocBlock支持retain、release,虽然retainCount始终是1,但内存管理器中仍然会增加、减少计数。copy之后不会生成新的对象,只是增加了一次引用,类似retain;
- 尽量不要对Block使用retain操作,原因是并没有Block_retain()这样的函数,而且objc里面的retain消息发送给block对象后,其内部实现是什么都不做。
5.2Block对外部变量的存取管理
5.2.1基本数据类型
- 1、局部变量。局部变量,在Block中只读。Block定义时截获变量的值,在Block中作为常量使用,所以即使变量的值在Block外改变,也不影响它在Block中的值。
1 int base = 100; 2 3 int (^myBlock)(int, int) = ^ (int a, int b) { 4 return base + a + b; 5 }; 6 7 base = 200; 8 9 // base:200 myBlock:10310 NSLog(@"base:%d myBlock:%d",base,myBlock(1,2));
- 2、全局变量或静态变量。在内存中的地址是固定的,Block在读取该变量值的时候是直接从其所在内存读出,获取到的是最新值,而不是在定义时截获的值。
1 static int base = 100; 2 3 int (^myBlock)(int, int) = ^ (int a, int b) { 4 5 return base + a + b; 6 7 }; 8 9 base = 200;10 11 // base:200 myBlock:20312 NSLog(@"base:%d myBlock:%d",base,myBlock(1,2));
- 3、__block修饰的变量。被__block修饰的变量称作Block变量。 基本类型的Block变量等效于全局变量、或静态变量。
注意:Block被另一个Block使用时,另一个Block被copy到堆上时,被使用的Block也会被copy。但作为参数的Block是不会发生copy的。
具体看示例:
1 typedef int (^MyBlock)(int,int); 2 3 - (void)funcBlockOne 4 { 5 int base = 100; 6 7 MyBlock blockOne = ^ (int a, int b) { 8 return base + a + b; 9 };10 11 // <__NSStackBlock__: 0x7fff5fbff868>12 NSLog(@"blockOne:%@", blockOne);13 14 [self funcBlockTwo:blockOne];15 }16 17 18 19 - (void)funcBlockTwo:(MyBlock)blockParam20 {21 // <__NSStackBlock__: 0x7fff5fbff868>22 // 和上面一样,说明作为参数传递时,并不会发生copy23 NSLog(@"blockParam:%@",blockParam);24 25 void (^blockTwo)(MyBlock) = ^ (MyBlock myBlock) {26 // 第一次:myBlock:<__NSStackBlock__: 0x7fff5fbff868>27 // 第二次:myBlock:<__NSStackBlock__: 0x7fff5fbff868>28 // 无论blockTwo在堆上还是栈上,作为参数的Block不会发生copy。29 NSLog(@"myBlock:%@",myBlock);30 31 // 第一次:blockParam:<__NSStackBlock__: 0x7fff5fbff868>32 // 第二次:blockParam:<__NSMallocBlock__: 0x100300ae0>33 // 当blockTwo copy到堆上时,blockParam也被copy了一分到堆上。34 NSLog(@"blockParam:%@",blockParam);35 };36 37 blockTwo(blockParam); // blockTwo在栈上38 39 // blockTwo:<__NSStackBlock__: 0x7fff5fbff808>40 NSLog(@"blockTwo:%@",blockTwo);41 42 blockTwo = [[blockTwo copy] autorelease];43 blockTwo(blockParam); // blk在堆上44 // blockTwo after copy:<__NSMallocBlock__: 0x100300ed0>45 NSLog(@"blockTwo after copy:%@",blockTwo);46 }
5.2.2Objective-C对象
不同于基本类型,Block会引起OC对象的引用计数变化。这里对static、global、instance、block变量非arc下的情况进行分析。还是先看示例:
.h文件:
1 @interface TestBlock : NSObject2 {3 NSObject *_instanceObj;4 }
.m文件:
1 @implementation TestBlock 2 3 NSObject *_globalObj = nil; 4 5 - (id) init 6 { 7 self = [super init]; 8 9 if (self)10 {11 _instanceObj = [[NSObject alloc] init];12 }13 return self;14 }15 16 - (void)mrcMemoryManage17 {18 static NSObject *_staticObj = nil;19 20 _globalObj = [[NSObject alloc] init];21 _staticObj = [[NSObject alloc] init];22 23 NSObject *localObj = [[NSObject alloc] init];24 __block NSObject *blockObj = [[NSObject alloc] init];25 26 typedef void (^MyBlock)(void) ;27 28 MyBlock aBlock = ^{29 NSLog(@"%@", _globalObj);30 NSLog(@"%@", _staticObj);31 NSLog(@"%@", _instanceObj);32 NSLog(@"%@", localObj);33 NSLog(@"%@", blockObj);34 };35 36 aBlock = [[aBlock copy] autorelease];37 aBlock();38 39 // 全局变量:1; 静态变量:1; 实例变量:1; 临时变量:2; block变量:140 NSLog(@"全局变量:%lu; 静态变量:%lu; 实例变量:%lu; 临时变量:%lu; block变量:%lu", (unsigned long)[_globalObj retainCount], (unsigned long)[_staticObj retainCount], (unsigned long)[_instanceObj retainCount], (unsigned long)[localObj retainCount], (unsigned long)[blockObj retainCount]);41 }
根据上面的结果,我们来分析一下:
- 全局变量和静态变量在内存中的位置是确定的,所以Block copy时不会retain对象。
- 实例变量在Block copy时也没有直接retain实例变量对象本身,但会retain self。所以在Block中可以直接读写_instanceObj变量。
- 临时变量在Block copy时,系统自动retain对象,增加其引用计数。
- block变量在Block copy时也不会retain。
- Block
- block
- Block
- block
- block
- block
- block
- block
- block
- Block
- block
- Block
- Block
- Block
- Block
- block
- Block
- block
- struts2文件下载出现Can not find a java.io.InputStream with the name的错误
- 使用NPOI的优势
- Stuts2 使用DispatchAction做一个简单的计算器
- Struts2 + MySql 数据库实现登录
- 如何将后缀为dat的数据文件插入数据库
- block
- .NET中 判断远程文件是否存在(可以跨域)
- 关于struts2中的拦截器和登陆验证
- iOS开发之静态库(四)—— 静态框架framework制作
- C/C++学习笔记24:结构体与共用体
- Android系统自带样式(android:theme)详解
- Android基本组件——1.Button、ImageView等的按下效果
- 快速排序简单实现
- linux驱动学习笔记(linux驱动头文件说明)