Introduction to Objective-C for Programmers, part IV
Introduction to Objective-C for Programmers contains the following parts already:
- Part I – literals
- Part II – class cluster pattern, categories, description method
- Part III – method swizzling
- Part IV – error model, delegates
- Part V – blocks
This is the fourth post in the series ‘Introduction to Objective-C for Programmers’, today I will touch on Objective-C error model and Delegates
Error Model
This is quite a big change in philosophy for a seasoned programmer especially coming from Java/C# and similar backgrounds. So in a single sentence, I can just write
Forget about exceptions!
I wrote it on purpose on a separate line so you can see clearly that I did not misspelled it probably ☺.
So why is that?
It all leads to memory management and ARC. If you think about how you could solve the reference counting and exceptions together it will quickly come up to you that its not that easy. Automatic Reference Counting is not exception safe by default, and throwing exceptions in the middle of the execution path somewhere in you program will most probably cause memory leaks.
It turns out that you can actually compile your program with a special flag which will turn on exception safe code to be generated for you but it will do so even for scenarios when no exceptions is thrown which is just a waste.
So, what do we do now?
First of all you should be aware of the fact that exception class is available for you, you just need to remember that you should use it for fatal errors only, like wrong initialization state etc
For other ‘exceptional’ cases you have two options.
First is to return 0/nil from the methods where you expect some object to be returned. This is primitive approach but works pretty well for some cases, eg. You have probably seen it already while using initWithValue method:
if ((self = [super init])) {
//the superclass initialized properly
}
return self;
}
Second, you have NSError class which lets you communicate failures.
For this two work you just add an argument to your methods which can error-out at runtime using a reference to an error pointer:
Example:
//return nil and set the error pointer if the error happens
if (someErrorCondition) {
NSMutableDictionary* details = [NSMutableDictionary dictionary];
[details setValue:@"ran out of money" forKey:NSLocalizedDescriptionKey];
*error = [NSError errorWithDomain:@"bankAccount" code:200 userInfo:details];
return nil;
}
//return proper value if all was ok
return YES;
}
The pattern to use that method goes as follows:
NSError* error = nil;
// try to execute the method and pass the pointer reference
id okOrNot = [self someTrickyStuffToExecute:amount error:&error];
if (!okOrNot) {
// inspect error
NSLog(@"%@", [error localizedDescription]);
}
You will encounter that pattern often while browsing the Objective-C code.
Personally, so far, I really like this approach, handling exceptions in large Java-based code base is like an alternative world for the application, in contrary with Objective-C, no standard exception handling and just a simple logic we can get rid of that problems, we just need to think a little bit more in advance.
Delegates
If you have written at least something more than Hello World in Objective-C you probably seen those already. If you want to get notified about some event happening in a separate object the delegate pattern is used all over the place in Objective-C world.
If you haven’t seen the delegates/data sources in action yet I recommend you find any tutorial on the net about TableView used in iOS application and you will get that covered quickly.
What I want to write here is how to create your own delegates and datasources for use in communication between your own classes or users of your library.
To cook our delegate example we need the following ingredients:
- Delegate protocol specifying required/optional methods any object needs to implement to become the actual delegate
- The object (usually some sort of service) which you will use in your classes and which will internally call the delegate methods
- The object which will become the actual delegate, eg. Your view controller or smth else
The simple example will be worth more than thousand words:
Lets say we have a service for downloading events, we create a delegate protocol for that service which will contain two methods, one being called when the events are downloaded and another one if smth goes wrong.
We start with creating our delegate protocol:
@class SPEventsFetcher;
@protocol SPEventsFetcherDelegate
- (void)networkFetcher:(SPEventsFetcher*)fetcher didReceiveData:(NSData*)data;
- (void)networkFetcher:(SPEventsFetcher*)fetcher didFailWithError:(NSError*)error;
@end
The ‘SPEventsFetcher’ class is our service which will do actuall downloading of our events.
#import <Foundation/Foundation.h>;
#import "SPEventsFetcherDelegate.h"
@interface SPEventsFetcher : NSObject
@property (nonatomic, weak)id delegate;
-(void)downloadAllEvents;
@end
#import "SPEventsFetcher.h"
@implementation SPEventsFetcher
-(void)downloadAllEvents{
//get the data from somwhere
NSData *data = [@"{MY EVENTS AS JSON CONTENT}" dataUsingEncoding:NSUTF8StringEncoding];
if ([_delegate respondsToSelector:@selector(networkFetcher:didReceiveData:)])
{
[_delegate networkFetcher:self didReceiveData:data];
}
}
@end
Its pretty simple, we have a method called ‘downloadAllEvents’ which will do actuall downloading, if all goes fine, it calls ‘didReceiveData’ on its delegate (if smth goes wrong it can call the ‘didFailWithError’)
In our view we can use our new and shiny delegate like all the others you’ve probably already seen:
#import "SPEventsFetcher.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
SPEventsFetcher *fetcher = [[SPEventsFetcher alloc] init];
fetcher.delegate = self;
[fetcher downloadAllEvents];
}
- (void)networkFetcher:(SPEventsFetcher*)fetcher didReceiveData:(NSData*)data{
NSLog(@"didReceiveData called in View Controller");
}
- (void)networkFetcher:(SPEventsFetcher*)fetcher didFailWithError:(NSError*)error{
NSLog(@"didFailWithError called in View Controller");
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
That’s pretty simple, ha? The important thing to remember is to use ‘weak’ attribute on the property for the actual delegate in our service
that’s because you will probably have a reference to the service itself in your view for example, and if both of them will be ‘strong’ you will and up with retain cycle and the memory for those objects would never be freed by ARC.
Another thing is to use the check:
{
[_delegate networkFetcher:self didReceiveData:data];
}
that’s because we haven’t marked any delegate methods as required, so if your object will be set as the delegate but will not implement the delegate methods we will end up with an error, doing the check you are making sure you are calling the delegate method only on objects actually able to respond to that message.
Introduction to Objective-C for Programmers contains the following parts already:
- Part I – literals
- Part II – class cluster pattern, categories, description method
- Part III – method swizzling
- Part IV – error model, delegates
- Part V – blocks