ℹ️ Select 'Choose Exercise', or randomize 'Next Random Exercise' in selected language.

Choose Exercise:
Timer 00:00
WPM --
Score --
Acc --
Correct chars --

DI Container Registration and Resolution

Swift

Goal -- WPM

Ready
Exercise Algorithm Area
1import Foundation
2import os.log
3
4// Define scopes for service lifetimes.
5enum Scope {
6case singleton
7case transient
8}
9
10// Protocol for any registered service.
11protocol Service {}
12
13// A type-erased factory function for creating service instances.
14typealias ServiceFactory = () -> Any
15
16// Stores information about a registered service.
17struct Registration {
18let scope: Scope
19let factory: ServiceFactory
20var singletonInstance: Any? // Holds the instance for singleton scope.
21}
22
23// A thread-safe Dependency Injection container.
24class DIContainer {
25private var registrations: [ObjectIdentifier: Registration] = [:]
26private let lock = NSRecursiveLock() // Use recursive lock for potential re-entrancy.
27
28// Registers a service with a given scope and factory.
29func register<ServiceType: Service, ConcreteType: ServiceType>(_ type: ServiceType.Type, scope: Scope, factory: @escaping (DIContainer) -> ConcreteType) {
30let identifier = ObjectIdentifier(ServiceType.self)
31let registration = Registration(scope: scope, factory: { factory(self) }) // Capture self for factory closure.
32
33lock.lock()
34defer { lock.unlock() }
35registrations[identifier] = registration
36}
37
38// Resolves a service of the specified type.
39func resolve<ServiceType: Service>(_ type: ServiceType.Type) -> ServiceType? {
40let identifier = ObjectIdentifier(ServiceType.self)
41
42lock.lock()
43defer { lock.unlock() }
44
45guard var registration = registrations[identifier] else {
46os_log("Service not registered for type %{public}s", log: .default, type: .error, String(describing: ServiceType.self))
47return nil
48}
49
50switch registration.scope {
51case .singleton:
52// If singleton instance doesn't exist, create it.
53if registration.singletonInstance == nil {
54registration.singletonInstance = registration.factory()
55// Update the registration in the dictionary as it now holds an instance.
56registrations[identifier] = registration
57}
58// Attempt to cast the singleton instance to the requested type.
59return registration.singletonInstance as? ServiceType
60case .transient:
61// For transient scope, always create a new instance.
62return registration.factory() as? ServiceType
63}
64}
65
66// Helper to resolve a service that requires the container itself.
67func resolve<ServiceType: Service>(_ type: ServiceType.Type, container: DIContainer) -> ServiceType? {
68return resolve(type)
69}
70}
71
72// --- Example Usage ---
73
74// Define some example services.
75protocol AnalyticsService: Service {}
76protocol NetworkService: Service {}
77
78class MockAnalyticsService: AnalyticsService {
79let id = UUID()
80init() { print("MockAnalyticsService initialized: \(id)") }
81}
82
83class RealNetworkService: NetworkService {
84let baseUrl: String
85init(baseUrl: String) { self.baseUrl = baseUrl; print("RealNetworkService initialized with \(baseUrl)") }
86}
87
88class AnotherService: Service {
89let dependency: NetworkService
90init(dependency: NetworkService) { self.dependency = dependency; print("AnotherService initialized") }
91}
92
93// Setup the DI container.
94let container = DIContainer()
95
96// Register services.
97container.register(NetworkService.self, scope: .singleton) { _ in RealNetworkService(baseUrl: "https://api.example.com") }
98container.register(AnalyticsService.self, scope: .singleton) { _ in MockAnalyticsService() }
99
100// Register a service that depends on another registered service.
101// Note: The factory closure receives the container to resolve its dependencies.
102container.register(AnotherService.self, scope: .transient) { container in
103guard let networkService = container.resolve(NetworkService.self) else {
104// In a real app, you might throw an error or handle this more robustly.
105fatalError("Could not resolve NetworkService for AnotherService")
106}
107return AnotherService(dependency: networkService)
108}
109
110// Resolve and use services.
111// let network1 = container.resolve(NetworkService.self)
112// let network2 = container.resolve(NetworkService.self)
113// print("Network services are the same instance: \(network1 === network2)") // Should be true for singleton
114//
115// let analytics1 = container.resolve(AnalyticsService.self)
116// let analytics2 = container.resolve(AnalyticsService.self)
117// print("Analytics services are the same instance: \(analytics1 === analytics2)") // Should be true for singleton
118//
119// let another1 = container.resolve(AnotherService.self)
120// let another2 = container.resolve(AnotherService.self)
121// print("AnotherService instances are the same: \(another1 === another2)") // Should be false for transient
122//
123// if let networkForAnother = another1?.dependency {
124// print("AnotherService dependency base URL: \(networkForAnother.baseUrl)")
125// }
Algorithm description viewbox

DI Container Registration and Resolution

Algorithm description:

This Swift code implements a thread-safe Dependency Injection (DI) container. It allows for registering services with different lifecycles (singleton or transient) and resolving them. The container uses type erasure and recursive locks to ensure thread safety and manage the creation and lifecycle of service instances, making it suitable for complex application architectures.

Algorithm explanation:

The `DIContainer` class manages the registration and resolution of services. It uses a dictionary `registrations` where keys are `ObjectIdentifier`s of service types and values are `Registration` structs. Each `Registration` stores the service's `Scope` (singleton or transient) and a `ServiceFactory` (a type-erased closure that creates an instance). A `NSRecursiveLock` is used to ensure thread safety during registration and resolution operations, preventing race conditions. When resolving a singleton service, the container first checks if an instance already exists; if not, it creates one using the factory and stores it. For transient services, a new instance is created every time `resolve` is called. The `register` method takes a factory closure that receives the `DIContainer` itself, allowing services to resolve their own dependencies. This design promotes loose coupling and testability by abstracting object creation and dependencies. Time complexity for registration is O(1) on average. Resolution complexity depends on the scope: O(1) for singleton (after first creation) and O(1) for transient (excluding the factory's complexity). Space complexity is O(N) where N is the number of registered services.

Pseudocode:

enum Scope { singleton, transient }
protocol Service {}
typealias ServiceFactory = () -> Any
struct Registration { scope, factory, singletonInstance? }

class DIContainer:
  private registrations: Dictionary<ObjectIdentifier, Registration>
  private lock: NSRecursiveLock

  function register<ServiceType, ConcreteType>(type, scope, factory):
    identifier = ObjectIdentifier(ServiceType)
    registration = Registration(scope, factory, nil)
    lock.lock()
    registrations[identifier] = registration
    lock.unlock()

  function resolve<ServiceType>(type):
    identifier = ObjectIdentifier(ServiceType)
    lock.lock()
    registration = registrations[identifier]
    lock.unlock()

    if registration is nil:
      log error
      return nil

    if registration.scope == .singleton:
      if registration.singletonInstance is nil:
        instance = registration.factory()
        // Update registration with instance
        lock.lock()
        registrations[identifier]?.singletonInstance = instance
        lock.unlock()
      return registration.singletonInstance as? ServiceType
    else if registration.scope == .transient:
      return registration.factory() as? ServiceType
    else:
      return nil