Thursday, August 30, 2012

Grand Central Dispatch in iOS

What is GCD?
GCD is a low level C API that allows programmers to take advantage of multiple cores in iOS devices, without worrying about which core they execute. GCD achieves a lot of its capabilities using dispatch queue's that are nothing more than thread pools. iOS does not allow you to interact with threads directly. We (programmers) talk to dispatch queue's and they in turn deal with tasks to do the job

Why should you care?
Multiple reasons why you should.
1. Makes the application responsive when doing time consuming tasks
2. Works extremely well with BLOCKs
3. Take advantage of multi-core h/w support

How to do GCD programming
You need no additional frameworks to be added into your project for GCD. All that you need to know is look for dispatch_* APIs and that's it

APIs you will come across here
dispatch_get_main_queue
Returns the serial dispatch queue associated with the application’s main thread.

dispatch_get_global_queue
Returns a well-known global concurrent queue of a given priority level.

dispatch_async
Submits a block for asynchronous execution on a dispatch queue and returns immediately.

dispatch_sync
Submits a block object for execution on a dispatch queue and waits until that block completes.

dispatch_release
Decrements the reference (retain) count of a dispatch object.


Basic GCD

- (IBAction)basicGCDButtonTouched:(id)sender {
    /*
     * Change the que from main queue to the newly created queue you will find out that the code
     * takes a lot more time to execute and that's probably because ios waits on the new queue and then
     * when the main queue becomes available, move the new queue to main!
     * Better programming practice is to do all UI related communication in main queue
     */
    //dispatch_queue_t mainQueue = //dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_async(mainQueue, ^{
        [[[UIAlertView allocinitWithTitle:@"Basic GCD" message:@"A simple GCD message" delegate:nil cancelButtonTitle:@"Cancel" otherButtonTitles:nilnilshow];
    });
}


Now that was easy and you rarely get to see things to do like that in a real life. Let's say if you have 2 background tasks that need to be done and all the while UI should be responsive, here's a simple way to do that. In the case below we execute 2 functions in a queue other than main queue, so the application remains responsive throughout while those long running methods grind their way.


- (IBAction)nonUIGCDButtonTouched:(id)sender {
    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT0);
    NSLog(@"THREAD - 1");
    dispatch_sync(concurrentQueue, printFrom1To10000);
    
    NSLog(@"THREAD - 2");
    dispatch_sync(concurrentQueue, printFrom1To10000);
}

If you look at the output of the above code, you will notice that unless THREAD-1 completes, THREAD-2 would not have started. That's because it's a serial execution process. When we load a View/ViewController we talk to a lot of services, we can follow the above methodology to talk to the service. Do remember above call is serial, until the first call is completed, second one will not get invoked.

What if you wanted those methods not to be executed in a serial manner. No sweat here's how we do it


- (IBAction)nonUIGCDAsyncButtonTouched:(id)sender {
    /*
     * This will still block the main thread. How ever the two print operations/methods
     * will execute in parallel. 
     */
    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT0);
    NSLog(@"THREAD - 1");
    dispatch_async(concurrentQueue, printFrom1To10000);
    
    NSLog(@"THREAD - 2");
    dispatch_async(concurrentQueue, printFrom1To10000);
}

So far we have been talking about executing in different queue's what about the first promise GCD made - Free my UI for other operations? Here's a simple sample of the above with operable UI too


- (IBAction)gcdSerialButtonTouched:(id)sender {
    /*
     * Queue name should be unique across applications. Make sure you pick a name that will not conflict
     * with anything else. It will not be a bad idea to have a seperate class to generate unique names
     * for these queue's
     */
    dispatch_queue_t serial_queue = dispatch_queue_create("CHASE.serial.queue"0);
    /*
     * Although these methods are async, they will be processed serially
     * This is a wonderful way to execute things in an auxillary thread, serially
     */
    dispatch_async(serial_queue, ^{
        [self firstOperationEntryPoint:@"First operation"];
    });
    dispatch_async(serial_queue, ^{
        [self secondOperationEntryPoint:@"Second operation"];
    });
    dispatch_release(serial_queue);
    
}

In the above code I have invoked 2 other functions - firstOperationEntryPoint & secondOperationEntryPoint. Here 's the code for those


- (void)firstOperationEntryPoint:(id)paramObject {
    for(NSUInteger counter =0; counter<1000;counter++) {
        NSLog(@"Counter: %lu - Thread = %@", (unsigned long)counter, [NSThread currentThread]);
    }
    NSLog(@"Parameter: %@", paramObject);
    NSLog(@"Main thread: %@", [NSThread mainThread]);
    NSLog(@"Current thread: %@", [NSThread currentThread]);
}

- (void)secondOperationEntryPoint:(id)paramObject {
    for(NSUInteger counter =0; counter<1000;counter++) {
        NSLog(@"Counter: %lu - Thread = %@", (unsigned long)counter, [NSThread currentThread]);
    }
    NSLog(@"Parameter: %@", paramObject);
    NSLog(@"Main thread: %@", [NSThread mainThread]);
    NSLog(@"Current thread: %@", [NSThread currentThread]);
}


Above code executes in serial fashion. If we want them to be in non-serial mode then group the calls and execute them using dispatch_group_async API

Now that's a nice way to get started with groups. Let's say you have a bunch of call that need to be executed and when all of them are complete you need to be notified. Here's how we can do that


- (IBAction)simpleGCDGroupingButtonTouched:(id)sender {
    dispatch_group_t taskGroup = dispatch_group_create();
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    dispatch_group_async(taskGroup, customQueue, printFrom1To10000);
    dispatch_group_async(taskGroup, customQueue, printFrom1To10000);
    
    dispatch_group_notify(taskGroup, mainQueue, ^{
        [[[UIAlertView allocinitWithTitle:@"GCD Async Grouping" message:@"Async jobs in this group are complete" delegate:nil cancelButtonTitle:@"Cancel" otherButtonTitles:nilnilshow];
    });
}


This is typically useful when you want to fire a lot of download or service calls. Instead of constantly asking for completion (poll), dispatch_group_notify is a better choice.

So far we had a look at low leve C API, if we want to handle it with higher level functionality iOS has fed us with NSoperation's


 (IBAction)asyncOperationsButtonTouched:(id)sender {
    NSOperationQueue *operationQueue = [[[NSOperationQueue allocinitautorelease];
    NSInvocationOperation *firstOperation = [[NSInvocationOperation allocinitWithTarget:self selector:@selector(firstOperationEntryPoint:) object:@"first operation"];
    NSInvocationOperation *secondOperation = [[NSInvocationOperation allocinitWithTarget:self selector:@selector(secondOperationEntryPoint:) object:@"second operation"];
    // if not for dependency, they will execute parallely
    [secondOperation addDependency:firstOperation];
    [operationQueue addOperation:secondOperation];
    [operationQueue addOperation:firstOperation];   
}


Here's a more complete example. If we want to download a file from a remote server and display it in view, we all know the time it will take will vary depending on the bandwidth available for download. We cannot keep the user waiting until the download is complete. If it takes too much time the user should be able to move out of the screen and can comeback later for download. How can we do that with dispatch_* APIs


- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
                                                                    0);
    [_activationIndicator setCenter:self.view.center];
    [_activationIndicator setHidesWhenStopped:YES];
    [self.view addSubview:_activationIndicator];
    [_activationIndicator startAnimating];
    dispatch_async(concurrentQueue, ^{
        __block UIImage* image = nil;
            dispatch_sync(concurrentQueue, ^{
            /* Download code */
            NSString *urlString = @"http://images.apple.com/mobileme/features/images/ipad_findyouripad_20100518.jpg";
            NSURL *url = [[NSURL alloc] initWithString:urlString];
            NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
            NSError *downloadError = nil;
            NSData *imageData = [NSURLConnection sendSynchronousRequest:urlRequest
                                                      returningResponse:nil
                                                                  error:&downloadError];
            if(downloadError == nil && imageData != nil) {
                image = [UIImage imageWithData:imageData];
            } else if(downloadError != nil) {
                NSLog(@"ERROR: %@", downloadError);
            } else {
                NSLog(@"No data could be downloaded from the URL!");
            }
                [NSThread sleepForTimeInterval:5];
        });
        
        dispatch_sync(dispatch_get_main_queue(), ^{
            /* Display it in UIImage */
            if(image != nil) {
                [_imageView setImage:image];
                [_imageView setContentMode:UIViewContentModeScaleAspectFit];
                [self.view addSubview:_imageView];
                [_activationIndicator stopAnimating];
                [_activationIndicator removeFromSuperview];
                NSLog(@"Image downloaded!");
            } else {
                NSLog(@"Image isn't downloaded for display!");
            }
        });
    });
}

- (void)displayAlert {
    dispatch_queue_t concurrentQue = dispatch_get_main_queue();
    dispatch_async(concurrentQue, ^{
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Sample Alert" message:@"From Sync block" delegate:nil cancelButtonTitle:@"Cancel" otherButtonTitles:nil, nil];
        [alertView show];
        [alertView release];
    });
    dispatch_async(dispatch_get_main_queue(), ^{
        [_activationIndicator startAnimating];
    });
}


Issues with GCD
To be updated shortly

Monday, August 27, 2012

Deep linking in iOS application


Deep linking is a concept where-in links within your application either link into some other sections/modules of your application or link into other applications with in your device. Links work the same way as they would in a web page the driving change for this behavior is the schema. Under normal circumstances URLs will have "Schema:domainname\address". In this case we customize the 'schema name' part to something we have identified within our application

How does it work?
The way we will see this at work is construct a simple UIWebView and render HTML page in it. Let say you display "www.apple.com". When you tap on the links in the web page (from your device), they are not intercepted by your application. It is handled within the webview, request forwarded to the domain server and finally reaches the web server, fetches data and comes back to the device in the same route. That's normal HTML request. On the other hand if you pick a custom URL (deep linked), URL will be stopped, verified against the settings in the current application and also in the list of applications registered with in the device for schema identification. If a match is found eithr the respective module is invoked from within the application or the external application is either awaken (from it's deep slumbers) or starts a new lease of life

Now enough of talk, let's get down to some code
Internal Links
1. Let's start with a very simple single-view application
2. I then created 4 additional UIViewControllers with xib file
3. Each of these view controllers will have a UIWebView, that will display contents from a website
4. 4th WebView will display a custom HTML text in the webview. Link in the webview has been modified to open a section from within the application
5. Open the .plist file for the project and add URL Types like the one appended. Feel free to choose a name of your choice

Changes to .plist file


Sample Webview for your reference. I created 2 more additional views that point to different websites like - www.techcrunch.com, www.engadget.com
Header file

// BIDFirstWebViewController.h



#import

@interface BIDFirstWebViewController : UIViewController <UIWebViewDelegate>
@property (retain, nonatomic) IBOutlet UIWebView *webview;
@property (retain, nonatomic) UIActivityIndicatorView *activityView;
@end

Implementation File


//
//  BIDFirstWebViewController.m
//

#import "BIDFirstWebViewController.h"

@interface BIDFirstWebViewController ()

@end

@implementation BIDFirstWebViewController
@synthesize activityView = _activityView;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)dealloc {
    [_webview release];
    [super dealloc];
}

- (void)viewWillAppear:(BOOL)animated {
    NSURL *url = [[NSURL alloc] initWithString:@"http://www.zdnet.com"];
    NSURLRequest *req = [[NSURLRequest alloc] initWithURL:url];
    [_webview setDelegate:self];
    [_webview loadRequest:req];
    //[_webview loadHTMLString:[self htmlTextForURL] baseURL:nil];
}

- (void)viewWillDisappear:(BOOL)animated {
    [_webview setDelegate:nil];
}

#pragma - WebViewDelegates
- (void)webViewDidStartLoad:(UIWebView *)webView {
    NSLog(@"Start download");
    UIActivityIndicatorView *activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
    [activityView setTag:10];
    [activityView setFrame:CGRectMake(10, 10, 25, 25)];
    [activityView setHidesWhenStopped:YES];
    [activityView startAnimating];
    [self.view addSubview:activityView];
    [activityView release];
}

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    UIActivityIndicatorView *activityView = (UIActivityIndicatorView*)[self.view viewWithTag:10];
    [activityView stopAnimating];
    [activityView removeFromSuperview];
    NSLog(@"Completed download");
}

@end


My custom webview - This view hosts a custom HTML page that will reference internal link
Header file

//
//  BIDFourthWebViewController.h
//

#import

@interface BIDFourthWebViewController : UIViewController <UIWebViewDelegate>
@property (retain, nonatomic) IBOutlet UIWebView *webview;

@end

Implementation file
//
//  BIDFourthWebViewController.m
//

#import "BIDFourthWebViewController.h"

@interface BIDFourthWebViewController ()

@end

@implementation BIDFourthWebViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
}

- (void)viewWillAppear:(BOOL)animated {
    [_webview setDelegate:self];
    [_webview loadHTMLString:[self htmlTextForURL] baseURL:nil];
}

- (void)viewWillDisappear:(BOOL)animated {
    [_webview setDelegate:nil];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma mark - webview dele gate implementation

- (void)webViewDidStartLoad:(UIWebView *)webView {
    UIActivityIndicatorView *activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
    [activityView setTag:10];
    [activityView setFrame:CGRectMake(10, 10, 25, 25)];
    [activityView setHidesWhenStopped:YES];
    [activityView startAnimating];
    [self.view addSubview:activityView];
    [activityView release];
}

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    UIActivityIndicatorView *activityView = (UIActivityIndicatorView*)[self.view viewWithTag:10];
    [activityView stopAnimating];
    [activityView removeFromSuperview];
}

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
    UIActivityIndicatorView *activityView = (UIActivityIndicatorView*)[self.view viewWithTag:10];
    [activityView stopAnimating];
    [activityView removeFromSuperview];
}

- (void)dealloc {
    [_webview release];
    [super dealloc];
}

#pragma mark - Private methods
- (NSString*)htmlTextForURL {
    NSString *htmlText = @"Hello World!
ZDNet";
    return htmlText;
}

@end


Now Open AppDelegate and make sure you have an implementation of the following

//
//  BIDAppDelegate.m
//

#import "BIDAppDelegate.h"

#import "BIDViewController.h"
#import "BIDUrlProcessor.h"

@implementation BIDAppDelegate
@synthesize navigationController = _navigationController;
- (void)dealloc
{
    [_window release];
    [_viewController release];
    [_navigationController release];
    [super dealloc];
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
    // Override point for customization after application launch.
    self.viewController = [[[BIDViewController alloc] initWithNibName:@"BIDViewController" bundle:nil] autorelease];
    self.navigationController = [[UINavigationController alloc] initWithRootViewController:self.viewController];
    //self.window.rootViewController = self.viewController;
    [self.window addSubview:self.navigationController.view];
    [self.window makeKeyAndVisible];
    return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application
{
    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}

- (void)applicationWillTerminate:(UIApplication *)application
{
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}


- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
    NSLog(@"Absolute :%@, Base :%@, Fragment: %@, Parameter: %@, Query: %@, Scheme: %@", [url absoluteString], [url baseURL], [url fragment], [url parameterString], [url query], [url scheme]);
    // parse URL for domain name and parameters
    // obtain parameter information
    if(![[url scheme] isEqualToString:@"cameraoperations"])
        return NO;

    NSDictionary *urlParameters = [BIDUrlProcessor getParametersFromUrlString:[url query]];
    // invoke view controller to update itself
    if([[urlParameters objectForKey:@"openpage"] isEqualToString:@"zdnet"]) {
        [self.viewController openViewForSegment:ZDNET];
    }
    return YES;
}

@end


My custom URL processor
Header file

//
//  BIDUrlProcessor.h
//

#import

@interface BIDUrlProcessor : NSObject
+ (NSDictionary*)getParametersFromUrlString:(NSString*)url;
@end

Implementation file


//
//  BIDUrlProcessor.m
//

#import "BIDUrlProcessor.h"

@implementation BIDUrlProcessor
+(NSDictionary*)getParametersFromUrlString:(NSString *)url {
    NSMutableDictionary *URLParameters = [[NSMutableDictionary alloc] init];
    if(url == nil || [url length] <= 0)
        return nil;
    // split on '&'
    NSArray *parameters = [url componentsSeparatedByString:@"&"];
    for (NSString *parameter in parameters) {
        NSArray *nameValuePair = [parameter componentsSeparatedByString:@"="];
        NSString *parameterName = [nameValuePair objectAtIndex:0];
        NSString *parameterValue = [nameValuePair objectAtIndex:1];
        [URLParameters setObject:parameterValue forKey:parameterName];
    }
    return URLParameters;
}
@end

Before you execute the application, make sure you set up a breakpoint in AppDelegate for the method - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
Now execute the application and browse to the view controller that displays the custom HTML, tap the link in the page, you will see the breakpoint in AppDelegate fired. That's it. Have fun with internal links

External Link
Now that we have an application with registered custom scheme, we can create another single-view application that will open the above application from a link.

Let's start by creating a simple Single-View application and drop a webview in it. This view controller resembles pretty much the same as the Custom webview we created for previous exercise. When the user taps on the link in this webview, iOS will look for the registerd schema if it find one associated with an installed application, it will open the application, if there is no matching schema registered, no action will be taken

Here's the sample ViewController I have in the new application
Header file
//
//  BIDWebViewController.h
//  OpenApplications
//

#import

@interface BIDWebViewController : UIViewController <UIWebViewDelegate>

@property (retain, nonatomic) IBOutlet UIWebView *webview;
@end

Implementation File

//
//  BIDWebViewController.m
//  OpenApplications
//

#import "BIDWebViewController.h"

@interface BIDWebViewController ()

@end

@implementation BIDWebViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
        [_webview setDelegate:self];
    }
    return self;
}

- (void)viewWillAppear:(BOOL)animated {
    [self setTitle:@"Open Custom application"];
    [_webview loadHTMLString:[self htmlTextForURL] baseURL:nil];
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)dealloc {
    [_webview release];
    [super dealloc];
}

#pragma mark - UIWebviewDelegate implementation
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
    // guess, not needed
}

- (void)webViewDidStartLoad:(UIWebView *)webView {
    // guess, not needed
}

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    // guess, not needed
}

#pragma mark - Private methods
- (NSString*)htmlTextForURL {
    NSString *htmlText = @"Hello World!
ZDNet";
    return htmlText;
}
@end


With these code changes in place, execute the application, tap on the link, and see this application open another application from a web link.

Hope you enjoyed reading this article and code snippet. Cheers