How can you simplify your code using blocks.
All iOS developers are familiar with the
Delegate pattern. It is an object oriented pattern widely used in Cocoa Touch frameworks. Most of time the delegate pattern is used to express callback code. Code that will be handled once a button is clicked or once datasource is initialized.
Since iOS4 and with the introduction of Blocks, it opens a new style of callback programming. If you're coming from functional language like Lisp, Clojure or like me, from Groovy, you know what a closure is. Simply put, Blocks (iOS term for Closures) encapsulate a piece of code, enclosing with it, variable that are in scope at the time the block is declared. Blocks can greatly simplify code. They can help you reduce code and more importantly they allow you to declare what going to happen at the same time than defining your code. Let's take a common use case and compare with Delegate approach versus with Block approach. For the Block code sample our example is base on
ARGenericTableViewController reusable component.
Our use case is taken from AeroGear TODO app for server side and for the client, let's look at
iOS client source code. I extracted from this sample app, the display of two tables: I'd like to focus on the implementation of two kinds of list: one is a dynamic list with standard cell, the next one is detailled view with section and custom cell.
|
TODO task list + task detail screens |
Using our
cloud hosted backend for TODO, you can add tasks with the web interface. Let's write an iOS client app to visualize the tasks list.
Source code could be find on github.
Delegate version
When implementing a UITableViewController, you need to define datasource delegate function
tableView:cellForRowAtIndexPath: This method is called for each row. It's the developer duty to customize the cell depending for each section depending on the row index.
In the case of a list of tasks (using one type of cell -besides standard one), our method
tableView:cellForRowAtIndexPath: is not doing much. But when defining detailed task table which has sections and inside section different types of cell, we end up with:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSUInteger section = [indexPath section];
NSUInteger row = [indexPath row];
UITableViewCell *cell;
switch (section) {
case AGTableSectionTitle: {
EditCell *titleCell = [EditCell cellForTableView:tableView withClassName:@"EditCell"];
titleCell.txtField.text = [self.task objectForKey:@"title"];
cell = titleCell;
break;
}
default:
{
...
}
case AGTableSectionDescr:
{
...
break;
}
case AGTableSectionDueProjTag:
{
switch (row) {
case AGTableSecDueProjTagRowDue:
{
...
break;
}
case AGTableSecDueProjTagRowProj:
{
...
break;
}
case AGTableSecDueProjTagRowTag:
{
...
break;
}
}
break;
}
}
return cell;
}
With the usage of switch your McCabe cyclomatic complexity increases :(
You will see how ARGenericTableView helps the cause.
Notice that for accessing property we chosen to subclass UITableViewCell (EditCell), at least we can keep the initialization of the cell shorter.
Always in a worry to keep it short, we define a method
cellForTableView:withClassName: for each custom cell. The method is using
dequeueReusableCellWithIdentifier: internally allowing reuse of cell.
Blocks version
Let's revisit our TODO app using UITableViewController to use Blocks.
1. Add pod dependency
2. Inherit from ARGenericTableViewController instead of UITableViewController
@interface AGTaskViewController : ARGenericTableViewController
@property (strong, nonatomic) id task;
@end
3. Initialise your callback blocks:
- (void)viewDidLoad
{
[super viewDidLoad];
ARTableViewData *tableViewData = [[ARTableViewData alloc] init];
[tableViewData addSectionData:[self sectionTitle]];
[tableViewData addSectionData:[self sectionDescription]];
[tableViewData addSectionData:[self sectionDueDateProjectTag]];
self.tableViewData = tableViewData;
}
- (ARSectionData *)sectionTitle {
NSString *name = NSStringFromClass([EditCell class]);
ARSectionData *sectionData = [[ARSectionData alloc] init];
[self.tableView registerClass:[EditCell class] forCellReuseIdentifier:name];
ARCellData *cellData = [[ARCellData alloc] initWithIdentifier:name];
[cellData setCellConfigurationBlock:^(EditCell *cell) {
cell.txtField.text = self.task[@"title"];
}];
[sectionData addCellData:cellData];
return sectionData;
}
All you code definition goes in the init method (viewDidLoad). You don't have to implement the delegate method
tableView:cellForRowAtIndexPath:, ARGenericTableViewController is doing it for you behind the scene.
ARGenericTable is a acting like a map of blocks that you register at init time. ARGenericTableViewController implements
tableView:cellForRowAtIndexPath:. For each cell, we retrieve cell configuration and call its associated block.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
ARCellData *cellData = [self.tableViewData cellDataAtIndexPath:indexPath];
NSString *CellIdentifier = cellData.identifier;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
Class TableViewClass = self.tableViewCellClassDict[CellIdentifier];
cell = [[TableViewClass alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Configure the cell...
if (cellData.cellConfigurationBlock) {
cellData.cellConfigurationBlock(cell);
}
return cell;
}
As you can see with the block approach, we have achieved much leaner code: no imbricated switch statement anymore. Notice the usage of
registerClass:forCellReuseIdentifier: which works with
dequeueReusableCellWithIdentifier: (iOS6 new pattern). Slight note on feature improvement for ARGenericTableViewController in order to be more flexible: everything is fine as long as your working with custom cell or standard cell using default style. However if you want to init with style of UITableViewCellStyleValue1, with the current implementation you can't.