Delegation: The One-to-One Callback Pattern Behind UIKit and Cocoa

Understand the delegate pattern precisely — how a protocol defines a delegate's contract, why delegate properties are conventionally weak, how optional protocol methods let a delegate implement only what it needs, and why this pattern is the dominant form of framework-to-app communication throughout UIKit and Cocoa.

So far this course has covered protocols as a general mechanism for declaring a contract of methods that a class promises to implement, independent of that class's position in any inheritance hierarchy. Delegation is the single most important practical application of that mechanism in the entire Objective-C ecosystem. Nearly every framework class that needs to hand control back to your application code — a table view that needs to ask how many rows to draw, a text field that needs to ask whether it should allow another keystroke, a network object that needs to report that a download finished — does it through a delegate. If you have written even a small amount of iOS or macOS code, you have already used this pattern dozens of times without necessarily having a precise account of why it is built the way it is. This lesson gives you that precise account: what a delegate protocol actually promises, why the delegate reference is weak rather than strong, how the pattern differs from the one-to-many broadcast model of NotificationCenter covered in the previous module, and how @optional methods combined with respondsToSelector: checks let a delegate opt into only the callbacks it actually cares about.

A Delegate Protocol Is a Contract for a Relationship

The earlier lessons on protocols (see 'What is a Protocol?') established that a protocol is simply a named list of method declarations with no implementation attached — a promise that any class adopting the protocol will provide those methods. Delegation takes that general mechanism and gives it a very specific shape: one object, the delegating object, defines a protocol that describes the moments at which it needs to talk back to some other object, and it declares a property — conventionally named delegate — typed to that protocol. Any object that wants to receive those callbacks adopts the protocol, implements the methods it cares about, and sets itself as the delegating object's delegate.

This is a narrower and more purposeful use of protocols than protocol-oriented APIs in general. A NSCopying-style protocol describes a capability an object has. A delegate protocol describes a specific, ongoing conversation between exactly two particular objects: this downloader and whichever object is currently acting as its delegate, this table view and whichever object is currently acting as its delegate. The protocol's method list is effectively the vocabulary of that conversation — every message the delegating object might ever need to send to whoever is standing in the delegate role.

Naming the protocol methods well is part of designing the contract. Convention is to prefix each method with the name of the delegating class, in lower camel case, so that when a delegate implements several different objects' delegate methods side by side, it is immediately obvious which framework object each callback belongs to. This is exactly why Apple's own delegate methods read as tableView:didSelectRowAtIndexPath: or connectionDidFinishLoading: rather than something generic like didFinish: — the receiving delegate is very often implementing more than one delegate protocol at once, and the method names alone need to disambiguate the source.

The Spec Example: DownloaderDelegate

The canonical minimal delegate protocol looks exactly like this, and it is worth memorizing the shape rather than just the specific method, because virtually every delegate protocol you will ever read or write follows this same template:

@protocol DownloaderDelegate <NSObject> declares a new protocol named DownloaderDelegate that itself conforms to NSObject — meaning anything adopting DownloaderDelegate is also guaranteed to respond to the basic NSObject protocol methods like respondsToSelector: and description, which turns out to matter a great deal once you start checking for optional methods later in this lesson. Inside the protocol body, - (void)downloaderDidFinish:(id)downloader; declares exactly one required instance method: any class that adopts DownloaderDelegate must implement a method with this exact signature, taking the downloader instance as its argument so the delegate can identify which downloader — if it happens to be managing more than one — just finished.

A hypothetical Downloader class would then declare @property (nonatomic, weak) id<DownloaderDelegate> delegate; and, at the moment its work completes, send [self.delegate downloaderDidFinish:self];. That single line is the entire callback mechanism: no return value is expected, no block is stored, nothing more elaborate than an ordinary Objective-C message send to whatever object currently occupies the delegate slot.

Why the Delegate Property Is Weak

Notice the weak in @property (nonatomic, weak) id<DownloaderDelegate> delegate; above — that is not a stylistic preference, it is close to an inviolable convention, and the earlier lesson on retain cycles explains precisely why it matters. In the overwhelming majority of real designs, the object that creates a delegating object and sets itself as its delegate is also the object that owns that delegating object through a separate strong reference. A view controller creates a Downloader, holds it in a strong property so it survives the length of the download, and sets downloader.delegate = self. If delegate were declared strong, the Downloader would now hold a strong reference back to the view controller — the exact Parent/Child retain-cycle shape covered previously, just with the delegating object and its delegate playing the two roles. Neither object could ever be deallocated, because each is keeping the other's retain count above zero.

Declaring delegate as weak breaks that cycle at exactly the right point. The Downloader does not own its delegate — it merely needs a way to reach it — so the reference expressing 'who do I report to' should never be the reference keeping that object alive. Ownership flows in one direction only: the view controller owns the Downloader (via its own strong property), and the Downloader merely knows how to find its delegate without holding it alive. If the view controller is deallocated for some other reason while a download is still in flight, the weak delegate reference automatically becomes nil, downloaderDidFinish: is simply never delivered to a dangling pointer, and nothing crashes.

This is worth generalizing beyond the single example: any time you write a delegate property, default to weak and only deviate from that default with a specific, well-understood reason. It is one of the few places in everyday Objective-C where the 'correct' answer is essentially always the same, which is exactly why seeing it done any other way should immediately catch your attention when reading code.

One-to-One Communication, Contrasted With Broadcast

A delegating object has exactly one delegate at a time — the delegate property holds a single reference, and setting a new delegate simply replaces whatever was there before. This is the defining structural feature of the pattern, and it is precisely what distinguishes delegation from the NotificationCenter broadcast model covered in the previous module. A notification is posted into the world with no idea who, if anyone, is listening; any number of unrelated observers can register for it, and the poster neither knows nor cares how many there are. A delegate callback is the opposite: the delegating object addresses its message to one specific, known collaborator, and that collaborator was deliberately assigned to the role by whoever set up the relationship.

This difference has real design consequences, not just a difference in syntax. Delegation is appropriate when there is a genuine, structured, ongoing relationship between two particular objects — a table view and the one controller that supplies its data, a text field and the one object validating its input. Broadcasting through NotificationCenter is appropriate when an event is inherently public and might matter to parts of the app that have no direct relationship to the object raising the event at all, such as 'the user logged out' or 'the app is about to enter the background.' Reaching for one pattern where the other fits naturally is a design smell covered again later in this module's comparison of communication patterns.

The <XDelegate> Convention Across UIKit and Cocoa

Once you recognize the shape of DownloaderDelegate, you will see it everywhere in Apple's frameworks, because it is the dominant idiom by which a framework class hands control back into your application code. UITableViewDelegate lets a table view ask you which row was tapped and how tall a given row should be. UITextFieldDelegate lets a text field ask whether it should begin editing or accept a particular character. NSXMLParserDelegate lets a streaming XML parser tell you, one event at a time, that it has found a new element, some character data, or the end of a document. In every single case, the naming convention is the same: the delegate protocol is named <ClassName>Delegate, the delegating class exposes a delegate property typed to it, and the delegate's methods are prefixed with a lower-camel-case version of the delegating class's name.

Recognizing this convention on sight is a genuinely useful skill when reading unfamiliar framework code: as soon as you see a protocol named SomethingDelegate and a class with a delegate property of that type, you already know the shape of the relationship without reading a line of documentation — one owning object, one collaborating delegate, a fixed vocabulary of callback methods, and (per the previous section) a weak reference holding the two together.

Optional Protocol Methods and respondsToSelector:

Not every delegate protocol method needs to be mandatory. A protocol can mark a section of its methods @optional, meaning an adopting class is free to implement none, some, or all of them, and the compiler will not complain about the ones it skips. This matters enormously in real delegate protocols, which often accumulate many callback methods over a framework's lifetime — a delegate that only cares about one specific event should not be forced to stub out a dozen methods it has no interest in.

The danger this introduces is that the delegating object can no longer assume every delegate implements every method. Sending [self.delegate someOptionalMethod] to a delegate that never implemented someOptionalMethod would, at best, silently do nothing useful and, in many cases, crash with 'unrecognized selector' — exactly the failure mode covered in the earlier lesson on dynamic messaging. The fix is to check first, using the respondsToSelector: method that every NSObject-conforming object provides: if ([self.delegate respondsToSelector:@selector(someOptionalMethod)]) { [self.delegate someOptionalMethod]; }. This is precisely why DownloaderDelegate was declared to conform to <NSObject> — without that, the compiler could not guarantee respondsToSelector: was even available to call on a id<DownloaderDelegate>-typed reference.

The pattern of marking secondary callbacks @optional and guarding every call to one with respondsToSelector: is so common in real Cocoa code that it is worth treating as a fixed idiom: required methods for the parts of the contract a delegating object cannot function without, optional methods with a guard for everything else a delegate might want to hook into but isn't obligated to.

Declaring and Adopting a Delegate Protocol

The delegate property's type, id<DownloaderDelegate>, means 'any object at all, as long as it conforms to DownloaderDelegate' — the compiler will warn if you assign an object that does not implement the required methods. weak is conventional for delegate properties precisely to avoid the retain-cycle shape covered in the earlier Retain Cycles lesson. Methods after @optional are not enforced by the compiler; call them only behind a respondsToSelector: check.

@protocol DownloaderDelegate <NSObject>
- (void)downloaderDidFinish:(id)downloader;
@optional
- (void)downloader:(id)downloader didUpdateProgress:(float)progress;
@end

@interface Downloader : NSObject
@property (nonatomic, weak) id<DownloaderDelegate> delegate;
- (void)start;
@end

Code Examples

This is the minimal, canonical shape of a delegate protocol: a name ending in 'Delegate', conformance to <NSObject> so respondsToSelector: is always available, and one required method whose first word names the event ('DidFinish') and whose sole argument hands the delegate a reference to the object reporting it.

• (void)downloaderDidFinish:(id)downloader;

Even though downloaderDidFinish: is a required method in the protocol, this defensive style still guards the call with respondsToSelector:, which is harmless for required methods and becomes essential the moment the protocol gains optional methods later. Passing self as the argument lets the delegate identify exactly which downloader reported in, which matters if the delegate is managing several downloaders at once.

if ([self.delegate respondsToSelector:@selector(downloaderDidFinish:)]) {

This shows the full relationship from the owning side: ViewController holds Downloader strongly (through its own strong property), and Downloader holds ViewController only weakly (through its delegate property). Ownership flows one way only, which is exactly what prevents the retain cycle discussed in this lesson. Adopting DownloaderDelegate in the class extension lets ViewController implement downloaderDidFinish: and be assigned directly to downloader.delegate without a cast.

self.downloader.delegate = self;

@protocol DownloaderDelegate <NSObject>
- (void)downloaderDidFinish:(id)downloader;
@end
@interface Downloader : NSObject
@property (nonatomic, weak) id<DownloaderDelegate> delegate;
@property (nonatomic, copy) NSString *urlString;
- (void)start;
@end

@implementation Downloader
- (void)start {
    // ... perform the download work ...
    [self finishedDownloading];
}

- (void)finishedDownloading {
    if ([self.delegate respondsToSelector:@selector(downloaderDidFinish:)]) {
        [self.delegate downloaderDidFinish:self];
    }
}
@end
@interface ViewController () <DownloaderDelegate>
@property (nonatomic, strong) Downloader *downloader;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.downloader = [[Downloader alloc] init];
    self.downloader.delegate = self;
    [self.downloader start];
}

- (void)downloaderDidFinish:(id)downloader {
    NSLog(@"Download finished: %@", downloader);
}
@end

Ownership Direction in a Delegate Relationship

The owning object (ViewController) holds the delegating object (Downloader) strongly; the delegating object holds its delegate only weakly, so no cycle forms.

ViewController  ── strong ──▶  Downloader
        ▲                            │
        └──────── weak ──────────────┘
                (delegate)

Important Rules

A delegate protocol declares the vocabulary of a specific, ongoing relationship between one delegating object and one delegate.

Declare delegate properties weak by default; a strong delegate property creates a retain cycle with the far more common ownership direction.

A delegating object has exactly one delegate at a time — contrast this with NotificationCenter's one-to-many broadcast to any number of observers.

Mark secondary, non-essential callbacks @optional, and always guard a call to an optional method with respondsToSelector: first.

Conform delegate protocols to <NSObject> so respondsToSelector: and other NSObject protocol methods are always available on the delegate reference.

Delegation in Everyday UIKit Code

Almost any non-trivial iOS view controller you open will show this exact pattern at least once: a class extension listing one or more <XDelegate> protocols after the class name, a handful of delegate methods implemented in the @implementation block, and a line somewhere in viewDidLoad or an initializer that sets tableView.delegate = self or textField.delegate = self. UITableViewDataSource and UITableViewDelegate together are perhaps the most-seen example in the entire framework — the table view has no idea how many rows exist or what should happen when one is tapped, and it asks its delegate and data source every single time it needs to know. Recognizing this shape on sight, and understanding precisely why the delegate reference is weak, is one of the most transferable pieces of Cocoa knowledge you can carry into any Objective-C codebase.

@property (nonatomic, strong) id<DownloaderDelegate> delegate;

@property (nonatomic, weak) id<DownloaderDelegate> delegate;

When the object that owns the delegating instance also sets itself as its delegate (the overwhelmingly common case), a strong delegate property creates a two-object retain cycle: the owner strongly retains the delegating object, and the delegating object strongly retains its delegate right back. Neither object's retain count can ever reach zero, so neither is ever deallocated. Declaring delegate weak breaks the cycle without changing anything about how the callback is sent.

[self.delegate downloader:self didUpdateProgress:0.5];

if ([self.delegate respondsToSelector:@selector(downloader:didUpdateProgress:)]) { [self.delegate downloader:self didUpdateProgress:0.5]; }

Calling an @optional protocol method directly, without first checking respondsToSelector:, assumes every possible delegate implements it. A delegate that only implemented the required downloaderDidFinish: method and skipped this optional one has no method body for the selector at all, and the message send will either do nothing useful or crash with an unrecognized-selector error, exactly as covered in the earlier lesson on dynamic messaging.

// Broadcasting a single, targeted event to one specific collaborator via NotificationCenter [[NSNotificationCenter defaultCenter] postNotificationName:@"DownloadFinished" object:self];

[self.delegate downloaderDidFinish:self];

When there is exactly one specific object that needs to know an event happened, and that object was deliberately assigned to the relationship ahead of time, a direct delegate call is simpler, type-checked by the compiler, and easier to trace than posting a notification that any unrelated part of the app could also observe. Reaching for NotificationCenter here adds indirection and loses the compile-time guarantee that the protocol's methods actually exist, without buying any benefit — nobody else needed to hear about this event.

Why is a delegate property conventionally declared weak rather than strong? A. weak properties are faster to access at runtime than strong properties B. Because the owner of the delegating object typically also sets itself as the delegate, and a strong delegate property would create a retain cycle with that ownership reference C. Because @protocol declarations do not support strong properties at all D. Because weak properties are required by ARC for any property typed with id<...>

B. Because the owner of the delegating object typically also sets itself as the delegate, and a strong delegate property would create a retain cycle with that ownership reference — The owning object almost always holds the delegating object strongly and sets itself as that object's delegate. If the delegate property were also strong, the two objects would hold each other alive in a retain cycle — exactly the failure mode covered in the earlier Retain Cycles lesson. Declaring delegate weak breaks the cycle.

In @protocol DownloaderDelegate <NSObject> - (void)downloaderDidFinish:(id)downloader; @end, what does conforming to <NSObject> provide? A. It makes downloaderDidFinish: optional automatically B. It guarantees the delegate is stored as a strong reference C. It guarantees basic NSObject protocol methods, such as respondsToSelector:, are available on any reference typed to DownloaderDelegate D. It allows the protocol to be adopted only by subclasses of NSObject, and by no other type

C. It guarantees basic NSObject protocol methods, such as respondsToSelector:, are available on any reference typed to DownloaderDelegate — Conforming a custom protocol to <NSObject> ensures that anything typed as id<DownloaderDelegate> is guaranteed to respond to the NSObject protocol's methods, including respondsToSelector:, which is exactly what lets the delegating object safely check for optional methods before calling them.

How many delegates can a single delegating object have assigned at once, and how does that contrast with NotificationCenter? A. An unlimited number of delegates, just like NotificationCenter allows unlimited observers B. Exactly one delegate at a time, whereas a notification can be observed by any number of unrelated objects C. Exactly one delegate, and NotificationCenter is limited to exactly one observer per notification name as well D. Zero or one, but NotificationCenter notifications can only ever have exactly one observer

B. Exactly one delegate at a time, whereas a notification can be observed by any number of unrelated objects — A delegate property holds a single reference — setting a new delegate simply replaces the old one. This one-to-one relationship is the defining contrast with NotificationCenter's broadcast model, where any number of unrelated observers can register for the same notification.

A protocol marks a method @optional. What must the delegating object do before calling that method on its delegate? A. Nothing extra — @optional methods behave identically to required methods at the call site B. Cast the delegate to (id) first so the compiler ignores missing implementations C. Check [self.delegate respondsToSelector:@selector(...)] first, and only send the message if it returns YES D. Re-declare the method as required in a category before calling it

C. Check [self.delegate respondsToSelector:@selector(...)] first, and only send the message if it returns YES — Because an @optional method might not be implemented by every adopting class, calling it unconditionally risks sending a message the delegate has no implementation for. respondsToSelector: checks at runtime whether the delegate actually implements that selector before the message is sent.

Which of these is the best real-world description of the <XDelegate> naming convention seen throughout UIKit and Cocoa (e.g. UITableViewDelegate, NSXMLParserDelegate)? A. A private naming scheme with no consistent meaning across different Apple frameworks B. A convention where a protocol named <ClassName>Delegate pairs with a delegate property on ClassName, describing a one-to-one contract for that class to hand control back to application code C. A convention used only for parsing-related classes like NSXMLParser D. A signal that the class does not conform to NSObject and must be used with performSelector: instead of ordinary messaging

B. A convention where a protocol named <ClassName>Delegate pairs with a delegate property on ClassName, describing a one-to-one contract for that class to hand control back to application code — Across UIKit and Cocoa, the <ClassName>Delegate protocol paired with a delegate property is the dominant idiom for a framework object to communicate back to whatever application object has been assigned the delegate role, from table views to text fields to XML parsers.

Design a TimerDelegate protocol for a hypothetical PomodoroTimer class. It should have one required method, timerDidComplete:, sent when a timer finishes, and one optional method, timer:didTickWithSecondsRemaining:, sent once per second while the timer runs. Declare PomodoroTimer's delegate property correctly, and write the internal method that fires the optional tick callback safely.

@protocol TimerDelegate <NSObject> - (void)timerDidComplete:(id)timer; @optional - (void)timer:(id)timer didTickWithSecondsRemaining:(NSInteger)seconds; @end @interface PomodoroTimer : NSObject @property (nonatomic, weak) id<TimerDelegate> delegate; @end @implementation PomodoroTimer - (void)tickWithSecondsRemaining:(NSInteger)seconds { if ([self.delegate respondsToSelector:@selector(timer:didTickWithSecondsRemaining:)]) { [self.delegate timer:self didTickWithSecondsRemaining:seconds]; } } @end

Conform the protocol to <NSObject>, mark the tick method @optional, declare the delegate property weak, and guard the optional call with respondsToSelector: before sending it.