[Download Xcode 9 project with full Objective-C source from GitHub.]
Introduction
I’m going to talk about “delegates” and “delegation.” I’ll lead you through a simple example of implementing the delegation design pattern in Objective-C, with full source code, and then show you a more sophisticated scenario. My intent here is to show you how delegation works without getting bogged down in some crazy complex example. Download the Xcode 9 project with full Objective-C source from GitHub to follow along.
I’ll show you how to build a user interface (UI) component, a status/progress indicator, which you can display on screen for processing-intensive tasks… AND I’ll show you how you can customize the behavior of the indicator by using delegation. For example, when the indicator starts, you could disable your UI; when the indicator stops, you could re-enable your UI; and, when the user taps the indicator, you could cancel processing-intensive tasks. Here’s the app we’ll build:
Recommended reading
In order to help you in understanding how I build my sample delegation code herein, you should read my articles on “Using Swift extensions to manage complexity, improve readability, and increase extensibility (UICollectionView, protocols, and delegates)” and “Understanding Swift 4 protocols and using them in your apps.”
Delegation
Let’s start with a layman’s definition of some terms.
The dictionary definition of “delegate” (Cambridge) is “to give a particular job, duty, right, etc. to someone else so that they do it for you.” The dictionary definition of “delegation” (Merriam-Webster) is “the act of empowering to act for another.”
According to Apple:
Delegation is a simple and powerful pattern in which one object in a program acts on behalf of, or in coordination with, another object. The delegating object keeps a reference to the other object–the delegate–and at the appropriate time sends a message to it. The message informs the delegate of an event that the delegating object is about to handle or has just handled. The delegate may respond to the message by updating the appearance or state of itself or other objects in the application, and in some cases it can return a value that affects how an impending event is handled. The main value of delegation is that it allows you to easily customize the behavior of several objects in one central object. …
… and also:
A delegate is an object that acts on behalf of, or in coordination with, another object when that object encounters an event in a program. The delegating object is often a responder object—that is, an object inheriting from NSResponder in AppKit or UIResponder in UIKit—that is responding to a user event. The delegate is an object that is delegated control of the user interface for that event, or is at least asked to interpret the event in an application-specific manner. …
The programming mechanism of delegation gives objects a chance to coordinate their appearance and state with changes occurring elsewhere in a program, changes usually brought about by user actions. More importantly, delegation makes it possible for one object to alter the behavior of another object without the need to inherit from it. The delegate is almost always one of your custom objects, and by definition it incorporates application-specific logic that the generic and delegating object cannot possibly know itself. …
Requirement: a protocol
In order for me write sample code implementing the delegation design pattern, I’ll need a “protocol.” Refer to my article on protocols and remember that, as Apple puts it (my emphasis added):
Delegation is a design pattern that enables a class or structure to hand off (or delegate) some of its responsibilities to an instance of another type. This design pattern is implemented by defining a protocol that encapsulates the delegated responsibilities, such that a conforming type (known as a delegate) is guaranteed to provide the functionality that has been delegated. Delegation can be used to respond to a particular action, or to retrieve data from an external source without needing to know the underlying type of that source.
Apple emphasizes that:
A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. The protocol can then be adopted by a class, structure, or enumeration to provide an actual implementation of those requirements. Any type that satisfies the requirements of a protocol is said to conform to that protocol. …
Explaining the code
NOTE: Keep referring to my prose and quotations above to remind yourself about the very important terminology — words, definitions — I’m using in the following discussion.
Piece: the delegating object
I’m going to build a class that implements a status/progress indicator, the StatusWheel
class, something that can be displayed on screen to tell the user, “The app is performing some process that may delay the app’s responsiveness.” This will be the “delegating object,” as defined above, that “keeps a reference to the other object–the delegate–and at the appropriate time sends a message to it.”
Piece: the delegate protocol
A protocol needs to be defined. Remember that the delegation “design pattern is implemented by defining a protocol that encapsulates the delegated responsibilities, such that a conforming type (known as a delegate) is guaranteed to provide the functionality that has been delegated” (my emphasis added). This will be the StatusWheelDelegate
protocol.
Piece: the delegate
I’m going to subclass a UIViewController
and make it adopt — conform to — the the delegate protocol StatusWheelDelegate
. Remember that the delegation “design pattern is implemented by defining a protocol that encapsulates the delegated responsibilities, such that a conforming type (known as a delegate) is guaranteed to provide the functionality that has been delegated” (my emphasis added). I’ll call this class ViewController
(my, how original).
Code demo
Here’s what the app I’m building does:
Here’s the app’s output to the console:
1 2 3 4 5 |
Status wheel was added to screen. Status wheel was TAPPED. Status wheel was removed from screen. Status wheel was added to screen. Status wheel was removed from screen. |
Writing the code
Piece: the delegating object
Let’s look at the code for the delegating object, StatusWheel
, which can display a status/progress indicator. I keep a weak
reference to the delegate
object (of type StatusWheelDelegate
) for two reasons: 1) to allow a StatusWheel
class instance to operate without a delegate (sending a message to nil doesn’t do anything in Objective-C), and 2) to prevent a retain cycle forming between the delegating object and delegate.
1 |
@property (nonatomic, weak) id <StatusWheelDelegate> delegate; |
Here’s StatusWheel.h
— and note the weak
“delegate” property:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#import <UIKit/UIKit.h> #import "StatusWheelDelegate.h" @interface StatusWheel : NSObject // Note the "weak" delegate @property (nonatomic, weak) id <StatusWheelDelegate> delegate; - (id)initWithOwner: (UIViewController*)viewController; - (void)show; - (void)hide; @end |
Here’s StatusWheel.m
— and note where the delegate
variable is sending messages like [self.delegate wasTapped:self];
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
#import "StatusWheel.h" @interface StatusWheel () @property (strong, nonatomic) UIActivityIndicatorView *statusIndicator; @property (strong, nonatomic) UIView *backingView; @end @implementation StatusWheel - (id)init { self = [super init]; if (self) { return self; } return nil; } - (id)initWithOwner: (UIViewController*)viewController; { self = [self init]; if (self) { self.statusIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; self.statusIndicator.alpha = 0.0; CGFloat screenWidthCenter = [UIScreen mainScreen].bounds.size.width/2; CGFloat screenHeightCenter = [UIScreen mainScreen].bounds.size.height/2; self.statusIndicator.center = CGPointMake(screenWidthCenter, screenHeightCenter); self.statusIndicator.color = [UIColor whiteColor]; int backingViewX = self.statusIndicator.frame.origin.x - 6; int backingViewY = self.statusIndicator.frame.origin.y - 6; int backingViewWidth = self.statusIndicator.frame.size.width + 12; int backingViewHeight = self.statusIndicator.frame.size.height + 12; self.backingView = [[UIView alloc] initWithFrame: CGRectMake (backingViewX, backingViewY, backingViewWidth, backingViewHeight)]; self.backingView.backgroundColor = [UIColor lightGrayColor]; self.backingView.alpha = 0.0; [viewController.view addSubview:self.backingView]; [viewController.view bringSubviewToFront:self.backingView]; [viewController.view addSubview:self.statusIndicator]; [viewController.view bringSubviewToFront:self.statusIndicator]; UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap:)]; [self.backingView addGestureRecognizer:singleTap]; return self; } return nil; } - (void)show { self.statusIndicator.alpha = 1.0; self.backingView.alpha = 1.0; [self.statusIndicator startAnimating]; [self.delegate wasAddedToScreen:self]; } - (void)hide { [self.statusIndicator stopAnimating]; self.statusIndicator.alpha = 0.0; self.backingView.alpha = 0.0; [self.delegate wasRemovedFromScreen:self]; } - (void)handleSingleTap:(UITapGestureRecognizer *)recognizer { [self.delegate wasTapped:self]; } @end |
Piece: the delegate protocol
StatusWheelDelegate
is the delegate protocol. You can conceptualize a protocol as a contract or promise that you can apply to a class, structure, or enumeration. Below, I will enter my ViewController
class into a contract with the StatusWheelDelegate
protocol, and the ViewController
class promises to fulfill the contract by implementing the methods or member variables that StatusWheelDelegate
requires be materialized or fulfilled, i.e., implemented.
Here’s StatusWheelDelegate.h
— and notice the forward declaration of class StatusWheel
(homework for you):
1 2 3 4 5 6 7 8 9 |
@class StatusWheel; @protocol StatusWheelDelegate <NSObject> - (void) wasRemovedFromScreen:(StatusWheel*) sender; - (void) wasAddedToScreen:(StatusWheel*) sender; - (void) wasTapped:(StatusWheel*) sender; @end //end @protocol OnScreenDelegate |
More homework: Why do you think I’ve specified a pointer to the StatusWheel
object in each method declaration?
Piece: the delegate
My ViewController
class adopts — conforms to — the delegate protocol StatusWheelDelegate
. Notice that:
1) it has an instance of StatusWheel
(StatusWheel* statusIndicator
);
2) it sets the StatusWheel
instance’s member variable delegate
to self
; and,
3) it provides customization of StatusWheel
by allowing me to do whatever I want when those protocol methods are called, like dismissing the status/progress indicator from screen when it gets tapped.
Here’s ViewController.h
:
1 2 3 4 5 |
#import <UIKit/UIKit.h> @interface ViewController : UIViewController @end |
Here’s ViewController.m
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
#import "StatusWheel.h" #import "ViewController.h" @interface ViewController () <StatusWheelDelegate> @property (strong,nonatomic) StatusWheel* statusIndicator; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. self.statusIndicator = [[StatusWheel alloc] initWithOwner:self]; self.statusIndicator.delegate = self; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (IBAction)startButtonTapped:(id)sender { [self.statusIndicator show]; } - (IBAction)stopbuttonTapped:(id)sender { [self.statusIndicator hide]; } - (void) wasRemovedFromScreen:(StatusWheel*) sender { NSLog(@"Status wheel was removed from screen."); } - (void) wasAddedToScreen:(StatusWheel*) sender { NSLog(@"Status wheel was added to screen."); } - (void) wasTapped:(StatusWheel *)sender { NSLog(@"Status wheel was TAPPED."); [self.statusIndicator hide]; } @end |
Wrapping up
I want you to thoroughly read and digest my initial discussion about delegation, follow some of the links, and read some of my suggested articles. Notice I’m not explaining every little detail. When looking at the code, I want you to think about what’s going on. Look something up in one of my references or find others if you don’t understand a concept. Write your own delegation code.
Finally, I had promised a more advanced example of delegation. Well, here it is: “Using Swift extensions to manage complexity, improve readability, and increase extensibility (UICollectionView, protocols, and delegates)”.