Friday, April 15, 2016

Objective C Exception Handling (@try --> @catch --> @finally)

Developers are always very happy with compile time error rather than run time errors. Because some times, they are so painful to address and fix. In any programming language to fix these run time exceptions have these try, catch blocks.

In this post let's see how to implement these for exception handling by taking an example exception and also how to avoid/fix that exception.

Let's discuss NSRangeException, which happens when we try to access an array object by passing an index value which is beyond the bounds of an array.


                         



Let's take an array and add some items into it. Whatever may be the data type of the items.


     NSMutableArray *arr = [[NSMutableArray alloc] init];
     [arr addObject:@"John"];
     [arr addObject:@"Paul"];

     [arr addObject:@"Frank"];



I have taken a mutable array and added some names. Here the array length is 3.

I have a method which gives me the user name from my array by passing the index as shown below.




 - (NSString*)getMeTheUserName:(int)index{

      NSString *username = @"";
      username = [arr objectAtIndex: index];

      return username;

 }


If we call this method by passing an index greater than 2, which is the last index of the array, app will get terminated with the below log.




 NSString *userName = [getMeTheUserName:8];


 *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -  [__NSArrayM objectAtIndex:]: index 8 beyond bounds [0 .. 2]'


 terminating with uncaught exception of type NSException



If you look at the log, It is saying 'uncaught exception'. There is an exception happened and we didn't caught it in the code, So app caught it at run time, thrown the exception and terminated the app as it couldn't proceed. It means as it can't access an item from the array with an index which is beyond the bounds of the array.

So, If we don't handle these exception, It will result in app crashes.

Let's handle this exception using the try, catch blocks and let's see what happens. Our aim here is now to avoid the app crash.

For this, I want to place the try block where I feel there is chance for this NSRangeException. Here it is at the below line.



 username = [arr objectAtIndex: index];


This line is very dangerous as we don't know what might get passed as index to this line. Now my method looks like below.




 - (NSString*)getMeTheUserName:(int)index{

     NSString *username = @"";

     @try {
         //Code that can potentially throw an exception
         username = [arr objectAtIndex: index];
     }
     @catch (NSException *exception) {
         //Handle an exception thrown in the @try block
         NSLog(@"%@",exception.name);
         NSLog(@"%@",exception.reason);
     }

     return username;

 }




We generally keep @try block where the code that can potentially throw an exception. @try blocks catches the exception and throws that to @catch block along with the exception parameter. In the @catch block we can see what is the exception name and the reason.

At this point If we call the method,


 NSString *userName = [getMeTheUserName:8];


app doesn't crash, instead the log statements in @catch block will get executed. 

That means, We successfully avoided the app crash.

But that is not enough. Right?

We need to handle this exception. Otherwise your app functionality may go wrong.

For this we have @finally block.




 
   @finally {
        //Code that gets executed whether or not an exception is thrown
    }


This @final block is used, when you want to change your logic when an exception happens.

Let's use this @finally block in our code and see what happens.



 - (NSString*)getMeTheUserName:(int)index{

    NSString *username = @"";

    @try {
        //Code that can potentially throw an exception
        username = [arr objectAtIndex: index];
    }
    @catch (NSException *exception) {
        //Handle an exception thrown in the @try block
        NSLog(@"%@",exception.name);
        NSLog(@"%@",exception.reason);
        //@throw exception;
    }
    @finally {
        //Code that gets executed whether or not an exception is thrown        
        NSLog(@"NSRangeException happend.");
        NSLog(@"Alert the user.");
    }

    return username;

 }



If you look at the default comment that comes with @finally block, The code in the @finally block will get executed when an exception is caught using the @try and @catch blocks.

It means, to mitigate the things instead of application logic going wrong or some other thing going wrong, We can alert the user on this exception or do some other changes to avoid expected functionality going wrong.

At this point If we call the method,



 NSString *userName = [getMeTheUserName:8];


app doesn't crash, instead the log statements in @catch and @finally blocks will get executed. 

Finally we have,

  • Avoided app crash by handling the exception using @try and @catch blocks      
  • Added the code to be executed to mitigate the things using the @finally block
Isn't these blocks are useful for runtime errors like this?


                         



Very useful. In fact it's a good practice to implement the blocks whenever developers find a piece of code that may cause exceptions.

Last but not the least, we may want our application to get crashed some times after executing the code in @finally block. Because we don't our to get proceed when a particular exception occurs.

In this case we can explicitly throw the exception using the @throw statement as shown below.




 - (NSString*)getMeTheUserName:(int)index{

    NSString *username = @"";

    @try {
        //Code that can potentially throw an exception
        username = [arr objectAtIndex: index];
    }
    @catch (NSException *exception) {
        //Handle an exception thrown in the @try block
        NSLog(@"%@",exception.name);
        NSLog(@"%@",exception.reason);
        @throw exception;
    }
    @finally {
        //Code that gets executed whether or not an exception is thrown        
        NSLog(@"NSRangeException happend.");
        NSLog(@"Alert the user.");
    }

    return username;


 } 


At this point If we call the method,



 
NSString *userName = [getMeTheUserName:8];


app crashes, but the code in the @finally block will get executed.

Hope this post is useful. feel free to comment in case of any queries.