Have you ever copied similar logic across classes and thought, “This feels wrong”? By the third copy of screen-specific data-loading code in an iOS app, your hands start to hesitate.
That is exactly when the Template Method pattern comes in handy.
In short, this pattern fixes the overall flow—the skeleton—in one place and lets each implementation fill in only the steps that vary. In Swift, a protocol extension provides a much cleaner way to do this than inheritance.
This article walks through what the Template Method pattern is, why protocol extensions are close to the ideal solution in Swift, and how to build the skeleton with real code.
Let’s start with the key takeaways
- The Template Method pattern separates a “fixed procedure + interchangeable steps”
- It is traditionally implemented through parent-class inheritance, but protocol extensions are more idiomatic in Swift
- Put the skeleton in the extension’s default implementation and leave varying parts as protocol requirements
- You avoid inheritance constraints such as single inheritance and tight coupling
What is the Template Method pattern?
The name sounds grand, but the concept is simple.
Think of a cooking recipe. “Prepare ingredients → prep → cook → plate” is a fixed sequence.
But the ingredients and cooking method differ from dish to dish.
The sequence—the skeleton—is the template method, while the dish-specific steps are the interchangeable parts.
Control the flow in one place and delegate only the details to the outside.
You could say that one sentence captures the entire Template Method pattern.
In code, the situation looks like this. The data-loading procedure is the same for every screen: start loading → request → parse → update the UI.
Only “which URL to request” and “how to parse the received data” vary.
Rewriting this shared procedure every time was the waste.
Why use a protocol extension instead of inheritance in Swift?
The original approach puts the skeleton in a parent class and has subclasses override specific methods. It is the familiar Java textbook pattern.
But implementing this through class inheritance in Swift comes with several drawbacks.
- Classes support only single inheritance. If you already have a parent, that’s it.
- You cannot use it with structs or enums at all
- The parent and child become tightly coupled, making later replacement difficult
Protocol extensions solve most of these problems.
Provide the skeleton method as a default implementation in an extension, and declare varying steps only as protocol requirements.
Then any struct or class that adopts the protocol gets the shared skeleton for free, without inheritance constraints.
Here is an example that models the data-loading procedure with a protocol. load() is the fixed skeleton, and the other two are the interchangeable parts.
protocol DataLoadable {
associatedtype Item
var endpoint: URL { get } // Different for each screen
func parse(_ data: Data) -> [Item] // Different for each screen
}
extension DataLoadable {
func load() async throws -> [Item] { // Fixed skeleton
let (data, _) = try await URLSession.shared.data(from: endpoint)
return parse(data)
}
}
The order of load() is controlled by the extension.
Each screen only needs to implement endpoint and parse in its own way.
Let’s build the skeleton in practice
Now let’s look at a type that adopts this protocol. Since adoption is not inheritance, it can be a struct.
struct ArticleLoader: DataLoadable {
let endpoint = URL(string: "https://api.example.com/articles")!
func parse(_ data: Data) -> [Article] {
(try? JSONDecoder().decode([Article].self, from: data)) ?? []
}
}
// Used: let articles = try await ArticleLoader().load()
As you can see, ArticleLoader does not implement load() directly.
Even so, it can call load() because the extension supplies the default implementation.
That is the power of a Template Method built with a protocol extension. When a new screen appears, create one more struct that implements only endpoint and parse.
After switching to this approach, I reduced the code for adding screens by more than half. With loading and error handling centralized, fixing bugs became much easier too.
One caveat: a protocol extension’s default implementation behaves somewhat differently from override. Even if the adopting type defines a method with the same name, it may be called unexpectedly through static dispatch if it is not declared as a protocol requirement.
So it is safest to declare every interchangeable step as a requirement in the protocol body.
A short Q&A
Q. Does that mean we can stop using inheritance-based Template Methods?
Where an existing class hierarchy is already in place, as with UIKit, overriding through inheritance can still be natural. Choose based on the situation.
Q. associatedtype feels intimidating.
If the return type is fixed, you can define this as a regular protocol without associatedtype. Use it only when you need generics.
Once you build the skeleton with a protocol extension, the inheritance problems you wrestled with can feel surprisingly unnecessary.
If you have logic you keep copying and pasting, extract just one piece into a protocol today. It will click quickly. You’ve got this!

