Delegate in Swift
When discussing a delegate I often see comparison with notifications, observing, and callbacks (closures or blocks). While solving many common problems this concepts are different. This article describes the delegation concept, how to implement proper delegate, best practices, and compares the delegate pattern with other approaches.
I prefer to separate delegation as a concept described in the “Gang of Four” book and the delegate pattern in Cocoa/Cocoa Touch.
The Delegation Concept
Delegate (verb) — entrust (a task or responsibility) to another person.
New Oxford American Dictionary
In programming terms this quote translates to entrust (a task or responsibility) to another object.
The delegation uses objects composition to allow customization and code reuse, and stands as an alternative to inheritance. Relationship between a delegating object and its delegate is an analogy to how a subclass forward methods to its superclass.
The difference that stands out is that inheritance is static and composition is dynamic. Using delegation allows changing objects behaviour at runtime.
Here I have an example hierarchy of objects. ShapeView
is an abstract class that provides stroke and fill colors. RectangleView
and EllipseView
implement drawing logic by overriding draw(_:)
method.
This is a valid use of inheritance. However delegation brings certain benefits to the table. In this example ShapeView
uses the delegate to hand over drawing to another object. This allows customizing drawing routine at runtime without the need to reinstantiate the view.
A method can refer to the current instance using self
keyword. Delegating object should pass itself to the delegate as the first argument: func drawShapeView(_ shapeView: ShapeView)
. This allows a delegate to refer back or differentiate between different objects if it stands as a delegate of multiple objects.
This is probably the only requirement the delegation concept impose. Implementation details rather depend on programming language and framework.
C# for instance has the System.Delegate
class and delegate
keyword.
Nor Swift or Objective-C has this. Instead we have conveniences on how to implement the delegate pattern.
The Delegate Pattern
The delegate pattern is very common in Cocoa and Cocoa Touch frameworks. Delegation used to notify about an event that will or was handled. Sometimes delegates can alter the impending event or block it completely.
Classic implementation of delegate in Objective-C looks like this:
This is the analogy in Swift:
Confirming to protocol in Objective-C is convenient using class extensions:
Using #pragma mark - MyDelegate
or // MARK: - MyDelegate
creates visual separation in the editor and the jump bar.
In Swift it is convenient to implement protocols in extensions.
Protocol Declaration
@protocol Delegate <NSObject>protocol Delegate : AnyObject
A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality.
The Swift Programming Language
Protocols are perfect fit for the delegation pattern because they provide right amount of control over requirements, not more or less than needed. This way a delegating object is not aware of implementation details of a delegate.
The Delegate
protocol inherits the NSObject
protocol in order to provide introspection for optional methods.
In Swift the Delegate
declared as a class-only protocol to use it as a weak
reference.
Alternative declaration is using informal protocols, typically categories on NSObject
, but this approach considered to be legacy and is not used in modern frameworks.
Property and Memory Management
@property (nonatomic, weak) id<Delegate> delegate;weak var delegate: Delegate?
Property in Objective-C is declared as id<Delegate>
:
id
is a pointer to any object, delegates are not limited toNSObject
subclasses;Delegate
inheritsNSObject
protocol to provide necessary runtime methods.
To prevent strong reference cycles, delegate properties are declared weak
. In Swift this is provided by using class-only protocols.
Why do we want to declare delegates as weak
references? Consider MVC pattern where a controller is a delegate of a view. Both instances hold a reference to each other. This can create strong reference cycle, which keeps a view and a controller alive. We need a weak
reference to resolve this.
Optionals
Delegates can have optional methods. In Objective-C group of optional methods is marked with @optional
keyword.
Before calling optional method we must ensure that method is implemented. In Objective-C we can use respondsToSelector:
because we inherit NSObject
protocol.
Swift has optional
modifier for interoperability with Objective-C. We must use@objc
for both protocol and optional method. This also requires arguments to be representable in Objective-C.
Overall declaration looks quite cumbersome. Calling optional method in Swift is convenient using optional chaining.
More Swifty was can be using protocol extensions to replace optional methods with default, empty implementations.
Naming Conventions
Delegate protocols are typically tightly connected with class declaration, without a class there is no use for a delegate. Nor Swift or Objective-C allows nested protocol declarations. Therefore the name of a delegate protocol must start with the class name.
Method names start with the class name (without a prefix) and first argument must be the delegating object itself.
This allows to refer back and useful when we want to provide default value or handle multiple delegating objects.
Method names should include auxiliary verbs (should, will, has, did) to identify a transpired or impending event.
Exceptions are common for cases where a delegate alters the impending event or provides additional information.
Delegate and Data Source
A data source is like a delegate except that, instead of being delegated control of the user interface, it is delegated control of data.
Concepts in Objective-C Programming
The Concepts in Objective-C Programming guide is a bit outdated and nowdays delegation obviously used to handle events not only in the user interface.
Data sources follow the same conventions as delegates. Except when all methods in a delegate can be optional, a data source contains required methods (data source that does not provide data it not very useful 😊) .
Some objects in this data sources are very heavy, like UICollectionViewCell
, and UIViewController
. Using this pattern allows consumers to lazily query providers and reuse allocated instances.
Also separating objects into handling events and providing data makes logical sense.
Delegate and Inheritance
Subclassing a class that has a delegate can reveal some issues. Take look at inheritance hierarchy of theUITableView
.
This design is actually not correct. Consider this code.
UITableView
expects its delegate to be UITableViewDelegate
type. However we can provide the UIScrollViewDelegate
subtype. This breaks the substitutability principle: return value can be subtype, argument can not be supertype:
You can read more about Liskov Substitution Principle and Covariance and Contravariance by Mike Ash on the topic.
This code compiles and works of two reasons:
- It compiles because the code is imported from Objective-C. Swift won’t allow this kind of overriding;
- It doesn’t break at runtime because all methods in delegate are optional and implementation checks if an instance implements a method. This is not the case for required methods in a data source.
Clang has -Wincompatible-property-type
diagnostic flag resulting in warning in Objective-C:
property type A is incompatible with type B inherited from C
Swift has a diagnostics error override_property_type_mismatch
:
property ‘A’ with type ‘B’ cannot override a property with type ‘C’
So what to do when we need to add methods to a delegate?
The way is to create a separate delegate. The UITableView
actually has multiple delegates for specific features:
Alternatively, if you’re not subclassing, you can inherit protocol to implement separation of concerns. URLSession
is a good example. The delegate
has URLSessionDelegate
type that stands as a base class for rich hierarchy including delegates for data, download, and stream tasks.
View Controllers Presentation
Delegates are useful when it comes to view controllers presentation. Edit view controller can notify the delegate about completion or cancellation of its intent.
Presenting view controller sets itself as a delegate of a presented view controller. Upon receiving a callback it can reflect changes and dismiss a presented controller. This is a nice way to communicate back changes and it doesn’t depend on concrete presentation.
Similar Patterns
While delegation concept describes how to replace inheritance with composition, the delegate pattern in Cocoa and Cocoa Touch is used primary as an event handling mechanism.
Target-Action
Target-action has a very specific use in Cocoa and Cocoa Touch — handling events from NSControl
and UIControl
.
How it compares to a delegate? Target-action is more strict, its API requires to action (selector) to have specific signature. There is also no way to alter or block the impending event.
Target-action is provided by a base class. It supports multiple targets and is more dynamic. For example, when no target provided, a control can determine the target at runtime using responder chain.
Notifications
Notifications is communication mechanism. It doesn’t create explicit connection between instances, but acts through shared instance (a notification center). Notifications allow one to multiple connections and provide no way to alter or block the impending event.
Closures as an Alternative
Closures are probably closest alternative for delegation. We can replace the delegate in the ShapeView
with closures.
Closures are one to one relationship but it is simpler to make them one to many without touching Objective-C runtime.
Because a closure can have return value they can alter or block the impending event. This also makes closures alternative for a data source.
Closures provide better support for required and optional routines. With protocols we must use optional
and @objc
attributes. This limits abilities to use Swift types, requires an object implementing protocol to be Objective-C class, and uses Objective-C messaging. With closures the difference between required and optional is difference between non-optional and optional type.
Closures can be provided inline. This can bring benefit of having code “inline” but can lead to cluttering function scope when many closures need to be implemented.
Overall closures are Swiftier alternative to the delegate pattern.
Delegation is intensively used in Cocoa and Cocoa Touch frameworks and it is important to know it.
As you can see, it doesn’t take much to implement proper delegate pattern. Pay attention to memory management and test for optional methods. Following naming conventions will make your delegates look professional. And if you’re looking for and alternative to delegation closures are the best choice.
Thank you for reading. If you like the article, please follow me and share.
Show authors more ❤️ with 👏’s
And hope to see you next time 😊