Why use delegation?
Well when writing Objective-C programs in Xcode we are encouraged to use the MVC(Model View Controller) structure.In MVC a model cannot send messages directly to a specific controller.This can become a problem if data in your model changes independantly and you need to update this information in your view via the controller(The model must never communicate directly with the view).This is where a delegate comes in,it allows messages to be sent from the model to a controller that is not specified in the model but is rather specified by the controller,in other words the controller specifies itself.
If you dont really understand what im talking about so far ,don't worry.I didn't understand delegates either until I needed one,but hopefully by the end of this tutorial you will understand not only how to use a delegate ,but the reason you would want to use one.
Program breakdown
For this tutorial we will write a program that has a model that will change an integer value on a timer event and we will have a view with a label and a controller that will update the value displayed on the label.We will then use delegation to tell the controller when it needs to update the label by broadcasting messages from the model.
First things first ,lets open up Xcode and create a single view based iPhone application With ARC(Automatic Reference Counting) enabled,I tried doing this without ARC before and alot of things broke and I actually wasn't able to fix it.I am also using storyboards,you can use Xibs if you know what you are doing.
Next lets add a label to the view and link it to our controller by selecting assistant editor and ctrl+dragging it into the Controller code.Make it an outlet and name it textLabel.
Assistant Editor icon:
Creating an Outlet:
This should generate the following code in ViewController.h:
@property (weak, nonatomic) IBOutlet UILabel *textLabel;
Now lets create our model,create a new Objective-C class That is a subclass of NSObject and name it NumberLooper or anything you deem appropraite.
Now lets start coding,in NumberLooper.h add the following code:
#import <Foundation/Foundation.h>
@interface NumberLooper : NSObject{
NSTimer *timer;
int currentNumber;
}
@property (nonatomic,strong) NSTimer *timer;
-(void)startTimerLoop;
@end
#import "NumberLooper.h"
@implementation NumberLooper
@synthesize timer;
-(void)timerEvent:(NSTimer*)timer{
/*if the number is less than 256 add 1 and else set
the number to 0*/
if(currentNumber < 256)
currentNumber ++;
else
currentNumber = 0;
//Display the number in the console
NSLog(@"%d",currentNumber);
}
-(void)startTimerLoop{
if (!timer){
timer=[ NSTimer scheduledTimerWithTimeInterval:0.020 target:self
selector:@selector(timerEvent:) userInfo:nil repeats:YES ];
selector:@selector(timerEvent:) userInfo:nil repeats:YES ];
NSLog(@"Timer started.");
}
else {
NSLog(@"Timer is already running");
}
}
@end
#import "NumberLooper.h"
NumberLooper *nl = [[NumberLooper alloc] init];
[nl startTimerLoop];
As you can see the label is still not updating ,which is good because if it is then some sort of voodoo magic has occured because we haven't even written the delegate yet.I think its about time we do that.First we create a @protocol in the model in NumberLooper.h above the interface ,this lets us specify what messages are sent to the delegate:
@protocol NumberLooperDelegate <NSObject>
-(void)numberHaschangedTo:(int)number;
@end
@interface NumberLooper : NSObject{ ...
@property (nonatomic,strong) NSTimer *timer;
@property (nonatomic,strong) id <NumberLooperDelegate> delegate;
-(void)startTimerLoop;
@synthesize delegate;
//Display the number in the console
NSLog(@"%d",currentNumber);
//send the message to the delegate
[self.delegate numberHaschangedTo:currentNumber];
@interface ViewController : UIViewController <NumberLooperDelegate>;
@property (weak, nonatomic) IBOutlet UILabel *textLabel;
@end
#pragma mark - NumberLooperDelegate -
-(void)numberHaschangedTo:(int)number{
//set the textlabel text value to the number
textLabel.text = [NSString stringWithFormat:@"%d",number];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NumberLooper *nl = [[NumberLooper alloc] init];
nl.delegate = self;
[nl startTimerLoop];
}
I hope you now have a good understanding of how to use delegates and why you would use them so that you can now go and try them out in your own apps.
If your app did not compile or didn't work properly or you have any questiions or some part of the tutorial was unclear to you and you need help,feel free to comment,i will reply to questions when I have time.
Great tutorial. I have been struggling to grasp the concept.
ReplyDeleteCrystal clear
ReplyDeleteI tried to use this in my app. But it is not working. Do you mind looking at my code?
ReplyDelete--Yuval
You should first try the tutorial as a separate app to see if you can master the concept, after that adding it to your app should be easy.
Delete@property (nonatomic,strong) id delegate;
ReplyDeleteWhy you're setting it as strong? It should be weak.
Well if weak works for you thats great , but I have personally had issues where the delegate was forgotten when it was weak.
DeleteSetting weak will prevent you from retain cycles. I believe that with ARC you probably can use strong. Anyway I'm a bit old-fashioned. Please let me know about the issues with setting strong. Maybe I could explain.
DeleteExcellent tutorial. I appreciate you taking the time to write this, thank you!
ReplyDeleteWow you just saved me a ton of headaches!!! I was trying to create a CLLocationManager Singleton and everywhere I looked made it so confusing. After reading this very simple clear explanation of how to use delegates I'm off and running.
ReplyDeleteThanks a million!
RON
One of best explanation availbale on internet, thanks so much!
ReplyDeleteAgreed. Brilliant explanation.
ReplyDeleteThank's, now I can make my own delegate.
ReplyDeleteGreat tutorial! :) Now I know how delegate works. thanks.
ReplyDelete