Completion handler APIs using blocks
With SP08 release, the OData SDK introduced block based completion handler APIs. The new APIs are used to open the online store and for scheduling execution requests.
Before we go into the specific APIs, let us take a quick look at blocks in iOS. In a nutshell, blocks are segments of code that can be passed to methods, just like you would pass values.Quoting from https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html:
“A block represents a distinct unit of work, combining executable code with optional state captured from the surrounding scope.”
One of the most common uses of blocks are as completion handlers. As specified in https://developer.apple.com/library/ios/featuredarticles/Short_Practical_Guide_Blocks/, completion handlers are callbacks that allow a client to perform some action when a framework method or function completes its task.Consider a function that needs to make a callback at a later time. To be able to use a block as a completion handler, the block must be passed as an argument to the function, so that when it finishes execution, the block is simply called. It is essential that the block is the last parameter of the function.
For example, here is the declaration of a function:
–(void)someFunction:(int)paramOne andCompletion: (void(^)(NSString* blockParam))completionBlock;
Below is the function implementation:
-(void)someFunction:(int)paramOne andCompletion: (void(^)(NSString * blockParam))completionBlock{
NSString *msg = @”Example of using blocks as completion handlers.”;
completionBlock(msg);
}
Consider the code below where the above function is invoked:
[self someFunction:5 andCompletion:^( NSString * blockParam){
// Define the block code here, thus only when ‘someFunction’ is invoked, the completion
// handler block is defined.
NSString *finalString = [NSString stringWithFormat:@”%@Blocks make life easy.”,blockParam];
NSLog(@”%@”,finalString);
}];
The final string printed out :
Example of using blocks as completion handlers. Blocks make life easy.
With the above basics, lets take a closer look at the block based API for the store opening. The block API for opening the store is :
-(void) openStore:(void (^)(id<SODataStoreAsync> store, NSError* error)) completionBlock;
A call to openStore using this API would be as follows:
SODataOnlineStore* onlineStore = [[SODataOnlineStore alloc] initWithURL:[NSURL URLWithString:self.endpointUrl] httpConversationManager:self.logonHandler.httpConvManager];
[onlineStore openStore:^(id<SODataStoreAsync> store, NSError *error) {
if(!error)
{
[self getODataEntries];
} else {
NSLog(@”%@”,error.description);
}
}];
If the store was opened via the old delegate based APIs ( as shown below) , we would need to use the SODataOnlineStoreDelegates methods. As you can see the code with blocks is much succinct and readable than using delegates.
SODataOnlineStore* onlineStore = [[SODataOnlineStore alloc] initWithURL:[NSURL URLWithString:self.endpointUrl] httpConversationManager:self.logonHandler.httpConvManager];
[onlineStore setOnlineStoreDelegate:self];
NSError *error = nil;
BOOL success = [onlineStore openStoreWithError:&error];
#pragma mark – SODataOnlineStoreDelegates
-(void)onlineStoreOpenFinished:(SODataOnlineStore *)store{
[self getODataEntries];
}
-(void)onlineStoreOpenFailed:(SODataOnlineStore *)store error:(NSError *)error{
NSLog(@”%@”,error.description);
}
The SDK also provides a list of completion block based network request APIs. ( Check the ODataOnlineStore.h header file for the full list.) These APIs in particular eliminate a lot of application specific code that are required when using delegates. eg. When we use the SODataRequestDelegate methods, in each method such as the requestFinished: method, there is no need to query which kind of request was invoked ( create , update or delete) , the collection being queried etc.
Below is an example of an update request with completion handler block APIs and the delegate APIs for comparison. The block based code is much easier than the requestFinished: delegate method where we have to verify if the request mode was indeed update before proceeding to process the result.
Block based update request:
[onlineStore scheduleUpdateEntity:entity options:nil completion:^(id<SODataRequestExecution> requestExecution, NSError *error) {
if (!error)
NSLog(@”Update Successful”);
}];
Delegate based update request:
[onlineStore scheduleUpdateEntity:entity delegate:self options:nil];
#pragma mark – SODataRequestDelegates
– (void) requestFinished:(id<SODataRequestExecution>)requestExecution {
NSError * error = nil;
id<SODataRequestParam> requestParam = requestExecution.request;
id<SODataRequestParamSingle> request = (id<SODataRequestParamSingle>)requestParam;
if ([requestParam conformsToProtocol:@protocol(SODataRequestParamSingle)])
{
if (request.mode == SODataRequestModeUpdate)
{
if (!error)
NSLog(@”Update Successful”);
}
}
}
Based on the above examples , using completion-block based APIs has the following advantages:
- There is no need to adopt protocols, or implementing delegate methods. This results in reduced code.
- Since the completion handler block code is always defined when the method is invoked, it results in simpler, more readable code. Additionally, the completion handler can directly access variables existing locally in the scope where it is defined.
- The completion-block based network request APIs in the SDK are easier to implement as they require less context–specific checks as with the delegate methods.
One final but very important point – you could choose to use the delegate or the block based APIs in your app, but do not mix the two flavors.
I hope this blog provided a good introduction to the completion-block APIs and help you take advantage of it in your next app.
See you in the next blog!