1 minute read

Protocols And Delegates In Swift For iOS Beginners

Swift protocols and delegates are core building blocks for organising iOS apps. They help separate responsibilities, define clear communication between objects, and make code easier to test as a project grows from a school experiment into a published app.

For a junior developer in Australia, these ideas are especially useful when building practical apps for local users: a tram timetable for Melbourne, a budgeting tool for Sydney students, or a service that handles Australian privacy requirements. UIKit relies heavily on these patterns, so understanding them gives you a strong foundation for everyday iOS development.

Protocols Define Shared Behaviour

A protocol describes behaviour that a type promises to provide. It does not need to know whether the conforming type is a class, structure, or enumeration. You can think of it as a contract made up of required properties, methods, or other capabilities.

protocol Printable {
    var title: String { get }
    func printDetails()
}

Any type that adopts Printable must provide title and printDetails():

struct Book: Printable {
    let title: String

    func printDetails() {
        print("Book: \(title)")
    }
}

The protocol focuses on what a type can do rather than how it does it. This makes code flexible. A view model, a network service, and a mock object could all conform to the same protocol while using completely different implementations.

Protocols are also useful for dependency injection. Instead of making a screen depend directly on a concrete API client, define a WeatherService protocol and pass in a real or test implementation. That approach is valuable when testing an app that displays conditions for Perth, Brisbane, or Atyrau without making network requests every time.

Delegates Send Events To Another Object

The delegate pattern is a communication technique in which one object reports events or requests decisions from another object. The object producing the event is often called the delegating object, while the object receiving it is the delegate.

UIKit uses this pattern throughout iOS development. A UITableView asks its data source for rows and informs its delegate when a user selects one. A UITextField can notify its delegate when editing begins or when the return key is pressed. A CLLocationManager reports location updates through its delegate.

A simple delegate usually consists of a protocol and a weak reference:

protocol DownloadDelegate: AnyObject {
    func downloadDidFinish()
}

final class Downloader {
    weak var delegate: DownloadDelegate?

    func completeDownload() {
        delegate?.downloadDidFinish()
    }
}

The AnyObject constraint means only class instances can adopt the protocol. This is necessary for a weak reference. Using weak helps prevent a retain cycle, where two objects keep each other in memory indefinitely.

Building A Delegate Relationship

A common delegate relationship has three steps. First, define the protocol with methods describing meaningful events. Second, give the sending object a weak delegate property. Third, make another class conform to the protocol and assign it as the delegate.

final class CheckoutViewController: UIViewController, DownloadDelegate {
    private let downloader = Downloader()

    override func viewDidLoad() {
        super.viewDidLoad()
        downloader.delegate = self
    }

    func downloadDidFinish() {
        print("Update the checkout screen")
    }
}

The downloader does not need to know that a view controller is listening. It simply reports that its work has finished. This loose coupling keeps business logic separate from presentation logic, which is helpful when a project has several screens or supports different iPhone sizes.

Delegates are usually best for one-to-one communication. If one event needs to reach many unrelated listeners, a closure, notification, Combine publisher, or AsyncStream may be a better choice. A callback-based design can be compared with other event approaches through this callback watcher example, although the implementation language and use case are different.

Choosing The Right Communication Pattern

Protocols and delegates overlap, but they are not identical. A protocol defines an interface, while delegation is one practical way to use an interface for object-to-object communication. The same protocol can support dependency injection, testing, or delegation.

Pattern Best use Typical direction Example
Delegate One object reports events to one listener One-to-one A text field informs its controller
Closure A small, local callback One-to-one A network request returns a result
Notification Several objects observe an event One-to-many App enters the background
Combine publisher A stream of changing values One-to-many Search results update as text changes
Async sequence Values arrive over time with concurrency One-to-one or many Live location updates

A delegate is often clearer than a large closure when several related events exist. For example, a file importer might report success, failure, progress, and cancellation through separate protocol methods. A single closure with many optional values would be harder to read and maintain.

The choice should reflect ownership and lifetime. If a screen owns a small operation, a closure may be concise. If a reusable UIKit component needs to communicate with its controller, a delegate protocol is usually a natural fit.

UIKit Examples Worth Practising

UITableViewDelegate and UITableViewDataSource are excellent examples for learning. The data source supplies content, while the delegate handles interaction such as row selection and row height. Keeping those responsibilities separate makes a list screen easier to extend.

func tableView(
    _ tableView: UITableView,
    didSelectRowAt indexPath: IndexPath
) {
    tableView.deselectRow(at: indexPath, animated: true)
    print("Selected row \(indexPath.row)")
}

The same ideas appear in collection views, map views, document pickers, and navigation controllers. When building a local transport app, a table view could show train departures from Sydney or tram services in Melbourne. The screen can ask a data source for display information while a delegate responds to taps.

Protocols also improve previews and tests. A screen can depend on a RouteProviding protocol rather than a live transport client. During development, a fake provider can return fixed routes, avoiding unreliable mobile data and making UI tests repeatable.

Safety, Privacy, And Australian App Context

Good protocol design should account for the data an app handles. An app collecting names, addresses, health information, or location may need to consider the Australian Privacy Act 1988 and its Australian Privacy Principles. A protocol such as LocationPermissionHandling can keep permission decisions separate from the view controller and make those flows easier to test.

Local product expectations matter as well. Users may pay in Australian dollars, use Commonwealth Bank or other local payment services, and expect clear receipts under Australian Consumer Law. A protocol for payment processing lets the interface work with a test gateway during development instead of sending real transactions.

Everyday context can guide small design decisions. A student checking an app while commuting on a Sydney train may have weak connectivity, while someone in regional Queensland may wait longer for a response. Protocol-based services make it easier to provide cached data, loading states, and offline test scenarios without rewriting the UI.

Practical Habits For Junior Developers

When practising protocols and delegates, build small examples before applying the pattern to a large app. A junior developer can create a form, a list, or a download screen and identify which object owns the work and which object needs to receive updates.

Useful habits include:

  • Give protocols names that describe a capability, such as ImageLoading or SearchProviding.
  • Keep delegate methods focused on events and decisions related to one component.
  • Mark class-only delegate protocols with AnyObject and use weak references where appropriate.
  • Create mock conformances for tests instead of calling live services from every screen.
  • Prefer a closure or modern concurrency when a single short callback is all the feature needs.

It is also worth reading UIKit declarations in Xcode. Jump to UITableViewDelegate, inspect its optional methods, and notice how Apple separates data presentation from user interaction. Then recreate a smaller version in your own project and trace the communication from the sender to the receiver.

A Clear Mental Model For Swift Projects

A useful mental model is simple: a protocol describes a promise, and a delegate is an object that fulfils that promise to receive information. The delegating object owns an operation; the delegate decides what should happen when an event occurs.

As projects become more advanced, the same foundation connects naturally with MVVM, dependency injection, unit testing, Combine, and Swift concurrency. You do not need every modern tool immediately. First make the relationship clear, give each object one responsibility, and choose communication that matches the number of listeners and the lifetime of the event.

Remember that protocols define capability, delegates create focused communication, and thoughtful ownership keeps Swift apps understandable, testable, and ready for real users.

Updated: