Target-Action and Callback Patterns: Choosing the Right Tool for the Job
Understand the target-action pattern used throughout Cocoa's UI controls, how it relates to selectors, and be able to choose confidently among delegates, blocks, notifications, and target-action based on the shape of the communication a given situation actually needs.
This module has now covered several distinct ways one piece of Objective-C code can tell another piece of code that something happened: a delegate calling back to its one assigned collaborator, a block capturing its surrounding context to run later, NSNotificationCenter broadcasting an event to any number of unrelated observers, and — the subject introduced in this lesson — target-action, the lightweight pattern that powers almost every interactive control in Cocoa and UIKit. Having four different tools for what sounds like the same underlying problem is not redundancy; each pattern makes a different tradeoff between simplicity, flexibility, and how many listeners are expected, and a large part of writing idiomatic Objective-C is picking the pattern whose shape actually matches the communication you need. This lesson introduces target-action on its own terms, then places all four patterns side by side using the exact comparison the specification lays out, so that by the end you have a working decision procedure rather than four isolated facts.
Target-Action: The Pattern Behind Every UIControl
Target-action is the pattern used by UIKit's UIControl subclasses — UIButton, UISlider, UISwitch, and the rest — to report user interaction back to your code. A control holds two pieces of information about what should happen when an event occurs: a target, which is the object that should receive the message, and an action, which is a selector naming the method to send that object. When the qualifying event fires — a button tap, a slider drag — the control effectively does the equivalent of [target performSelector:action withObject:self], invoking exactly one method on exactly one designated object.
In practice you almost never write the performSelector: call yourself; UIKit wires it up for you through an API like [button addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];. This one line tells the button: when a touch-up-inside event happens, send the buttonTapped: message to self. The method signature convention for an action method, - (void)buttonTapped:(id)sender;, takes the control itself as its single argument — mirroring the delegate convention from the previous lesson of handing the receiving method a reference to whoever is reporting the event, so a single action method can distinguish between several different controls that share it.
The key structural feature of target-action, and the reason it is such a good fit for UI controls specifically, is its minimalism: it is exactly one selector, sent to exactly one target, in response to exactly one kind of event. There is no protocol to define, no multiple methods to implement, no broadcast to any number of listeners — just a direct, lightweight wire between 'this event happened' and 'call this one method.' That minimalism is a feature, not a limitation, for the enormous number of cases where a UI control genuinely only needs to report one thing to one place.
How Target-Action Relates to Selectors
Target-action only works because of the selector mechanism covered in the earlier Selectors lesson. The action a control stores is not a block, not a function pointer in the C sense, and not a direct reference to a method implementation — it is a SEL, the same lightweight, interned identifier for a method name that @selector(...) produces anywhere else in Objective-C. Storing a SEL rather than a direct reference is precisely what lets a control be built once and wired up to a completely different method by every different piece of code that uses it: the button doesn't know or care what buttonTapped: does, it just knows the name, and at event time it resolves that name against the target using the same dynamic dispatch machinery covered in the Dynamic Messaging and performSelector: lessons.
This is also why target-action, selectors, and performSelector: form a tightly related family rather than three unconnected topics. A SEL is the addressable identity of a method; performSelector: is one explicit way to invoke a method by that identity; target-action is a specific, structured application of exactly that same mechanism, packaged into a target object and an action selector and triggered automatically by the control in response to a UI event rather than by an explicit call in your own code.
With target-action now introduced alongside the delegate pattern from the previous lesson, blocks, and NotificationCenter, it is worth laying all four side by side, exactly as the specification does, because the comparison below is more useful as a single mental model than as four separate, disconnected definitions.
Delegate: A Direct, Structured Relationship
A delegate relationship is defined by a named protocol, assigned deliberately ahead of time, and it lasts for as long as the delegating object needs it — often the full lifetime of the delegating object itself. It is 'direct' in the sense that the delegating object always knows exactly who its one delegate is, and 'structured' in the sense that the protocol spells out, in advance, every distinct kind of event the delegate might be asked to handle, often across many separate methods with different signatures. This makes delegation the right tool whenever a relationship needs more than one kind of callback over time, and whenever it matters that exactly one specific, known collaborator handles them — a table view asking its delegate dozens of different questions over the view's lifetime is a textbook case.
Block: A Local Callback Tied to Its Call Site
A block is a callback defined inline, right where it will eventually be used, and it captures the variables and context of its enclosing scope automatically — no protocol, no separate delegate object, no property to wire up ahead of time. This makes blocks the natural fit for a single, self-contained asynchronous operation with exactly one completion point: [downloader fetchWithCompletion:^(NSData *data, NSError *error) { ... }]; reads, top to bottom, as 'do this thing, then when it's done, do this' — all in one place, with direct access to whatever local state the surrounding method already had in scope. Where a delegate protocol is defined once and reused across every instance of a class, a block callback is usually written fresh at each call site, tailored to exactly what that particular call needs to do next.
Notification: A Broadcast Event With No Direct Relationship
A notification posted through NSNotificationCenter has no concept of a specific recipient at all. The object posting it does not know, and does not need to know, whether zero, one, or fifty other objects are listening; it simply announces that something happened by name, and whichever objects have registered as observers for that name are individually informed. This total decoupling is exactly right for events that are inherently public within an application — the keyboard is about to appear, the app is entering the background, the user's authentication state changed — situations where several unrelated parts of the app might independently care about the same event, without any of them needing a direct reference to whichever part of the app raised it.
Target-Action: A Lightweight Event-to-Target Pattern
As covered earlier in this lesson, target-action sits between the highly structured delegate pattern and the fully decoupled notification pattern: it is more direct than a notification (a specific target is always named) but far lighter-weight than a delegate protocol (a single selector rather than a whole family of methods, and no protocol declaration required at all). This is exactly why it is the standard mechanism for wiring up simple UI controls — a single button tap rarely needs the ceremony of a delegate protocol, but it does need to go to one specific place, which rules out a bare notification.
Choosing the Right Pattern
With all four patterns laid out, the practical decision procedure becomes fairly direct. Reach for a block when you have a single asynchronous operation with one natural completion point and you want the calling code and the completion code to read together, in the same place, with shared access to local variables — a network fetch, an animation completion, a single-shot file read. Reach for a delegate when the relationship is ongoing and structured, with multiple distinct callback points over time, and there is exactly one object that should be consulted or informed — a table view's data source, a text field validating input as the user types. Reach for a notification when the event is genuinely public — something multiple, unrelated parts of the app might care about without any of them holding a reference to whoever raised the event, and without the poster needing to know or care who, if anyone, is listening. Reach for target-action for exactly what it was built for: wiring a single UI control's event to a single handler method, which is by far the most common case in ordinary view controller code.
These four tools are not mutually exclusive within a single class — a real UIViewController commonly uses all four at once: target-action for its buttons, a delegate for its table view, a block for a network call's completion handler, and NotificationCenter observers for keyboard or app-lifecycle events. The skill this lesson is really teaching is not memorizing four definitions, it is recognizing, for any given piece of communication in your code, whether it is one-to-one and structured, one-shot and local, public and broadcast, or a simple UI event — and then reaching for the pattern whose shape actually matches.
Wiring Up Target-Action
The action selector's conventional signature takes a single sender argument, so one method can be shared across multiple controls and still identify which one fired. forControlEvents: lets a single control send different actions, or the same action, for different kinds of interaction (touch up inside, value changed, and so on). Internally, the control resolves the SEL against the target dynamically at event time, using the same mechanism covered in the Dynamic Messaging lesson.
// Wiring the target and action
[button addTarget:self
action:@selector(buttonTapped:)
forControlEvents:UIControlEventTouchUpInside];
// The action method itself
- (void)buttonTapped:(id)sender {
NSLog(@"Tapped: %@", sender);
}Code Examples
This is the exact delegate protocol from the previous lesson, included here as the reference point for the comparison: a delegate relationship needs a named protocol, an adopting class, and a weak delegate property — considerably more ceremony than target-action, but appropriate when a relationship is ongoing and might grow more callback methods over time.
• (void)downloaderDidFinish:(id)downloader;
This is the target-action pattern at its most typical: one control, one target (self), one action selector, wired once in viewDidLoad. Compare the ceremony here with the DownloaderDelegate example above — there is no protocol to declare and no delegate property, because the relationship is a single event-to-method wire rather than an ongoing, multi-method contract.
The same underlying idea — 'something happened, now do something in response' — expressed through all four patterns side by side. Notice how the amount of ceremony scales with how structured or how public the relationship needs to be: target-action and blocks are the leanest, delegation adds a protocol for an ongoing structured relationship, and NotificationCenter trades away any direct relationship at all in exchange for reaching an unknown number of listeners.
// 4. Target-Action — lightweight, single UI event to a target
@protocol DownloaderDelegate <NSObject>
- (void)downloaderDidFinish:(id)downloader;
@end@interface ViewController ()
@property (nonatomic, strong) UIButton *downloadButton;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.downloadButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.downloadButton setTitle:@"Download" forState:UIControlStateNormal];
[self.downloadButton addTarget:self
action:@selector(downloadButtonTapped:)
forControlEvents:UIControlEventTouchUpInside];
}
- (void)downloadButtonTapped:(id)sender {
NSLog(@"Download button tapped: %@", sender);
}
@end// 1. Delegate — ongoing, structured, one known collaborator
downloader.delegate = self; // -> downloaderDidFinish:
// 2. Block — one-shot, local, defined at the call site
[downloader fetchWithCompletion:^(NSData *data, NSError *error) {
NSLog(@"Fetched %lu bytes", (unsigned long)data.length);
}];
// 3. Notification — broadcast, no direct relationship
[[NSNotificationCenter defaultCenter] postNotificationName:@"DownloadFinished"
object:self];
// 4. Target-Action — lightweight, single UI event to a target
[retryButton addTarget:self
action:@selector(retryTapped:)
forControlEvents:UIControlEventTouchUpInside];Comparison of Communication Patterns
The four patterns ordered by how structured and how direct the relationship is: a delegate is the most structured one-to-one contract, a block is the most local, a notification is the least direct (broadcast to any number of listeners), and target-action is the lightest-weight direct wire, purpose-built for UI events.
Delegate → direct structured relationship
Block → local callback
Notification → broadcast event
Target-Action → event to targetImportant Rules
Target-action stores a target object and an action SEL, and invokes that selector on that target when a qualifying event fires — the same selector mechanism covered in the Selectors lesson.
An action method's conventional signature takes the firing control as its single argument, letting one method serve several controls.
Use a block for a single asynchronous operation's one natural completion point, especially when the callback needs the surrounding local context.
Use a delegate for an ongoing, structured relationship with one specific collaborator and potentially many distinct callback points.
Use a notification for an event that is genuinely public — one that unrelated parts of the app might independently care about.
Use target-action for simple, direct UI event wiring where a single control needs to reach a single handler method.
All Four Patterns in One View Controller
A typical, moderately complex UIViewController in a real app uses every pattern covered in this module at once, and recognizing why each one was chosen is a good test of whether this lesson's model has actually stuck. Its buttons and sliders use target-action, because each is a simple, direct UI event with one handler. Its table view uses a delegate and data source, because the relationship is ongoing and structured across many distinct callback methods. A network call it makes uses a block completion handler, because the fetch is a single asynchronous operation with one natural finishing point. And it likely observes at least one NSNotificationCenter notification — UIKeyboardWillShowNotification is almost universal — because the keyboard's appearance is a genuinely public, broadcast event that many unrelated parts of the interface might need to react to independently.
[[NSNotificationCenter defaultCenter] postNotificationName:@"ButtonTapped" object:self]; // ...meanwhile the only thing that will ever care is this one button's owning view controller
[button addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
Posting a notification for an event that only one specific, already-known object will ever handle adds indirection with no benefit: any other part of the app could accidentally start observing the same notification name, the compiler cannot check that a matching observer even exists, and the relationship is harder to trace than a direct target-action wire or a direct method call would have been.
• (void)handleButton:(id)sender { // assumes 'sender' is always the same button, ignores which control actually fired [self doSomethingFor:self.theOneButtonIExpected]; } - (void)handleButton:(id)sender { // use sender itself, so the same method works correctly for every control wired to it [self doSomethingFor:sender]; }
The whole point of the sender argument in an action method's conventional signature is to identify which control actually fired, especially when one action method is shared across several controls via addTarget:action:forControlEvents:. Ignoring sender and assuming a single hardcoded control breaks the moment a second control is wired to the same action.
// A one-shot network fetch with a single completion point, modeled as a full delegate protocol @protocol FetcherDelegate <NSObject> - (void)fetcher:(id)fetcher didFinishWithData:(NSData *)data; @end
[fetcher fetchWithCompletion:^(NSData *data, NSError *error) { // handle the result right here, with full access to this method's local variables }];
Building an entire delegate protocol — with its own property, adoption syntax, and weak-reference bookkeeping — for a single operation that has exactly one completion point and no ongoing relationship afterward is more ceremony than the situation needs. A block completion handler expresses the same one-shot callback in a single line, right at the call site, with direct access to local context that a separate delegate method would not have.
In the target-action pattern, what two pieces of information does a control such as UIButton store to know how to report an event? A. A block to execute and a dispatch queue to run it on B. A target object and an action selector (SEL) naming the method to send it C. A notification name and a userInfo dictionary D. A delegate protocol and a weak reference to the conforming object
B. A target object and an action selector (SEL) naming the method to send it — Target-action stores a target object and an action, which is a SEL — the same lightweight selector identifier covered in the earlier Selectors lesson. When the qualifying event fires, the control sends that selector to that target.
How does target-action relate to the earlier lesson on selectors? A. It does not relate — target-action is implemented entirely with blocks internally B. The action a control stores is a SEL, the same selector identifier produced by @selector(...), resolved dynamically against the target when the event fires C. Target-action replaced selectors entirely in modern Objective-C D. Selectors are only used for class methods, while target-action always calls instance methods, making the two unrelated
B. The action a control stores is a SEL, the same selector identifier produced by @selector(...), resolved dynamically against the target when the event fires — The action stored by a control is exactly a SEL, the same construct @selector(...) produces. Target-action is a structured, event-triggered application of the same dynamic dispatch-by-selector mechanism covered in the Selectors and Dynamic Messaging lessons.
According to the comparison table — Delegate / Block / Notification / Target-Action — which pattern is described as a 'broadcast event'? A. Delegate B. Block C. Notification D. Target-Action
C. Notification — Notification is the broadcast pattern: it is posted with no knowledge of who, if anyone, is listening, and any number of unrelated observers can independently react to it, in contrast to the direct, structured, or single-target relationships the other three patterns describe.
Which situation is the best fit for a block-based completion handler rather than a delegate protocol? A. A table view that needs to ask its owner dozens of different questions over its entire lifetime B. A single asynchronous network fetch with one natural completion point, where the response should be handled with direct access to the calling method's local variables C. An event that many unrelated parts of the app need to independently observe D. A button tap that should call exactly one handler method
B. A single asynchronous network fetch with one natural completion point, where the response should be handled with direct access to the calling method's local variables — Blocks are the natural fit for a one-shot operation with a single completion point, since they are defined inline at the call site and automatically capture the surrounding local context — no protocol declaration or delegate property is needed for a relationship that exists for the length of one call.
A custom control needs to report 'value changed' to exactly one designated object, with minimal setup and no protocol declaration required. Which pattern best fits, and why? A. Notification, because it requires the least code to post B. Delegate, because every callback in Cocoa should be modeled as a protocol C. Target-action, because it is the lightest-weight pattern for wiring a single UI event to a single target without declaring a protocol D. Block, because blocks are always preferred over any object-based callback
C. Target-action, because it is the lightest-weight pattern for wiring a single UI event to a single target without declaring a protocol — Target-action is purpose-built for exactly this shape: a single event reported to a single target through one selector, with none of the ceremony of declaring and adopting a delegate protocol, and none of the broadcast semantics of a notification.
For each of the following four situations, name the single best-fitting pattern (delegate, block, notification, or target-action) and justify the choice in one sentence: (1) a 'Cancel' button in a form that should dismiss the screen when tapped; (2) a single image-upload call that needs to report success or failure back to the exact code that triggered it; (3) an object that wants to be told every time the system's preferred content-size (accessibility text size) changes, an event many unrelated screens in the app might care about; (4) a custom UIPickerView-style control that needs to ask its owner, repeatedly over its lifetime, how many rows exist and what title each row should show.
(1) Target-action — a single UI event (tap) with one direct handler. (2) Block — a one-shot asynchronous operation with exactly one completion point, best handled right at the call site. (3) Notification — the event is genuinely public; many unrelated screens might independently care, and no direct relationship to a poster is needed. (4) Delegate — an ongoing, structured relationship with multiple distinct callback points (row count, row titles, and likely more) asked repeatedly of exactly one known collaborator.
Match each situation to the structural feature that defines its pattern: one-shot and local, ongoing and structured with one known collaborator, public and broadcast, or a single UI event to a single target.