As your iOS app grows, dependency management can become a real headache.
Eventually, you run into the Swift Service Locator pattern. Some find it convenient, while others dismiss it as an anti-pattern.
I found it most useful when applied selectively, only where it was truly needed, rather than adopted everywhere.
Here is the conclusion first.
Service Locator is not a complete alternative to DI (Dependency Injection); misused, it becomes an anti-pattern that hides dependencies. It is better viewed as a supporting tool.
Today, I’ll explain what this pattern is, why it gets criticized, and when it can still be useful, based on my experience.
What Is the Service Locator Pattern?
In one sentence, it is a way to retrieve the objects you need from a central registry.
You keep a single registry somewhere and look up objects there whenever you need them.
Constructor Injection supplies dependencies from outside, whereas Service Locator looks them up directly from inside.
The code should make the idea clear quickly.
// A structure that registers and retrieves services from a central registry
final class ServiceLocator {
static let shared = ServiceLocator()
private var services: [String: Any] = [:]
func register<T>(_ service: T) { services["\(T.self)"] = service }
func resolve<T>() -> T { services["\(T.self)"] as! T }
}
Register them once when the app starts.
The code that uses them retrieves the required dependencies like this.
// The view model directly 'looks up' its dependencies
final class FeedViewModel {
private let api: APIClient = ServiceLocator.shared.resolve()
// It works without injecting api into the constructor
}
As you can see, the biggest advantage is a cleaner constructor.
Why Is Service Locator Considered an Anti-Pattern?
There is one main reason: dependencies become hidden.
Let’s look at the FeedViewModel code above again. The constructor alone does not reveal that this class uses APIClient.
You only see it after opening the implementation. That becomes surprisingly inconvenient when collaborating.
The second problem is testing.
With constructor injection, testing is as simple as passing in a mock. With Service Locator, you must change global registry state, so tests can easily interfere with one another.
The third issue is runtime risk.
If you try to retrieve an unregistered dependency, the app crashes during execution rather than at compile time. The forced cast as! in the example above is exactly where this happens.
In summary:
| Comparison | Constructor Injection (DI) | Service Locator |
|---|---|---|
| Dependency exposure | Clearly visible | Hidden internally |
| Testability | High | Relatively low |
| When errors are detected | Compile time | Runtime |
| Constructor conciseness | More arguments | Clean |
When Is It Acceptable to Use?
It is not inherently bad. I found it useful in situations like these.
- A single service shared across the app, such as a logger or analytics tool
- When the constructor injection path is too deep and arguments keep being passed down
- A transitional phase while gradually introducing DI into legacy code
The third case is especially practical.
Adding constructor injection everywhere at once in existing code is a heavy lift. In that situation, using Service Locator as a temporary bridge and gradually moving to constructor injection felt safer.
These days, DI containers such as Swinject or Swift’s @Environment are more common than a pure Service Locator.
They still retrieve objects from a registry internally, but stronger registration validation and scope management reduce the risks.
Frequently Asked Questions
Q. How is it different from Singleton?
A Singleton makes one particular object global, while Service Locator acts as a ‘warehouse’ for multiple objects. They serve somewhat different purposes.
Q. Is it also used in SwiftUI?
SwiftUI’s @Environment and @EnvironmentObject are concepts quite similar to Service Locator. Apple is essentially providing a comparable approach at the framework level.
Q. What should be the default approach, then?
I recommend using constructor injection by default. Use Service Locator locally, only where it is truly necessary.
There is no single right answer to dependency management, but the direction is clear.
Make dependencies visible whenever possible, and use tools that hide them cautiously and only where necessary. Even that discipline makes maintenance much easier.

