关于Block之前有一篇文章已经写过一篇文章Object-C-代码块Block回顾,不过写的比较浅显,不能体现出Block在实际开发中的重要性,关于Block的基础知识,可以参考之前的博客。在实际开发中Block在回调过程中的是非常适合开发使用,不管是苹果的官方的接口还是一些第三方库的接口中都用到了Block回调。很多情况下Block和GCD一起使用,最常见的场景的就是App去后台取数据的过程中是需要时间,数据取成功之后我们才能更新UI页面,这就是最常见的回调的方式,也可以通过Notification来做,如果是单个用Notification没问题,如果请求比较多的情况的,代码量会上一个级别。
Block回调
简单的Block写法,返回类型 Block名称 参数,基本上符合方法的写法,先看一个最简单的Block写法:
int (^blockDemo)(int a,int b)=^(int a,int b){ return a+b; }; NSLog(@"BlockDemo的结果:%d",blockDemo(90,72));
最后的结果是162,简单明了,很容易看懂,现在我们先通过UITableView展示后台数据,效果如下:
ViewController中的代码,简单的实现了一下UITableView:
- (UITableView *)tableView { if (!_tableView) { _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 64, CGRectGetWidth(self.view.bounds) - 10, CGRectGetHeight(self.view.bounds) - 64) style:UITableViewStylePlain]; _tableView.rowHeight = 40.0; _tableView.sectionheaderHeight = 0.0; _tableView.sectionFooterHeight = 0.0; _tableView.dataSource = self; _tableView.delegate = self; } return _tableView; } -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ return [self.dataSource count]; } -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ UITableViewCell *cell=[[UITableViewCell alloc]init]; cell.textLabel.text=[self.dataSource objectAtIndex:indexPath.row]; return cell; }
通过FEDataService中的fetchData取出数据:
-(NSMutableArray *)fetchData{ NSMutableArray *mutableArray=[[NSMutableArray alloc]initWithObjects:@"博客园",@"FlyElephant",@"Http://www.cnblogs.com/xiaofeixiang",@"iOS技术交流群:228407086",nil]; return mutableArray; }
Controller中的调用:
self.dataService=[[FEDataService alloc]init]; self.dataSource=[self.dataService fetchData];
当时从后台取数据是需要时间的,而且网络不一定能取出数据,这个时候就可以通过Block进行回调,在DataService中重新定义了一个fetchDataSource方法:
-(void)fetchDataSource:(void(^)(NSMutableArray *array,NSError *error))fetchDataBlock;
注意这里的Block传参的写法,fetchDataBlock相当于是参数名,前面的是类型,实现中加入了GCD
-(void)fetchDataSource:(void (^)(NSMutableArray *, NSError *))fetchDataBlock{ dispatch_time_t time=dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC*(int64_t)1.0); dispatch_after(time,dispatch_get_main_queue() , ^{ NSMutableArray *mutableArray=[[NSMutableArray alloc]initWithObjects:@"博客园",@"FlyElephant",@"http://www.cnblogs.com/xiaofeixiang",@"iOS技术交流群:228407086",nil]; fetchDataBlock(mutableArray,nil); }); }
Controller中进行回调同样实现以上效果:
[self.dataService fetchDataSource:^(NSMutableArray *array,NSError *error){ if (!error) { self.dataSource=array; [self.tableView reloadData]; } }];
Block延伸
1.栈块,堆块和全局块
定义一个块的时候,其所占的内存区域是在栈中的,块只在定义它的那个范围有有效,我们可以先看一下下面的写法:
NSString *string=@"博客园FlyElephant"; void (^block)(); if ([string isEqualToString:@"iOS技术交流群:228407086"]) { block=^{ NSLog(@"keso"); }; }else{ block=^{ NSLog(@"http://www.cnblogs.com/xiaofeixiang"); }; }
先定义了block,之后在判断语句中对block进行赋值,最终栈中保存两个块的内存,在判断语句之外调用block有可能会把分配给块的内存覆盖,最终造成的结果就是有的时候正确,被覆写的时候就会造成程序崩溃,解决上面问题的方式我们可以通过block从栈内存中通过copy存储在堆内存中,代码如下:
NSString *string=@"博客园FlyElephant"; void (^block)(); if ([string isEqualToString:@"iOS技术交流群:228407086"]) { block=[^{ NSLog(@"keso"); } copy]; }else{ block=[^{ NSLog(@"http://www.cnblogs.com/xiaofeixiang"); } copy]; }
存储在堆中的块就变成了引用计算类型,当引用计数变成0在ARC的环境下的就会被系统回收,而栈中的内存是由系统自动回收的,所以第一段代码稳定性不能保证,还有一种是全局块,将全局块声明在全局内存中,编译期就已经确定,不需要每次用到的在栈中创建,全局块的拷贝是一个空操作,所以全局块不可能被系统回收。
2.通过typedef简化代码可读性
Block回调中我们发现传入一个块的对象写法有的时候看起来实在不是那么简单明了,我们可以通过typedef简化定义一个块:
typedef void (^FetchBlock)(NSMutableArray *dataSouce,NSError *error);
DataService中方法就可以简化了不少:
-(void)fetchDataSourceSimple:(FetchBlock)block;
实现代码和之前的block实现一样:
-(void)fetchDataSourceSimple:(FetchBlock)block{ dispatch_time_t time=dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC*(int64_t)1.0); dispatch_after(time,dispatch_get_main_queue() , ^{ NSMutableArray *mutableArray=[[NSMutableArray alloc]initWithObjects:@"博客园",@"FlyElephant",@"http://www.cnblogs.com/xiaofeixiang",@"iOS技术交流群:228407086", nil]; block(mutableArray,nil); }); }
行笔匆匆,难免遗漏,如有不当,多多指教~