Bird
Raised Fist0
iOS Swiftmobile~20 mins

Dependency injection in iOS Swift - Mini App: Build & Ship

Choose your learning style10 modes available
Build: Greeting Screen
This screen shows a greeting message using a service injected via dependency injection.
Target UI
-----------------------
| Greeting Screen      |
|---------------------|
| Hello, User!        |
|                     |
| [Change Greeting]   |
-----------------------
Create a GreetingService protocol with a method to get a greeting string.
Implement a DefaultGreetingService that returns a greeting message.
Inject the GreetingService into the GreetingViewController via initializer.
Display the greeting message in a UILabel.
Add a UIButton labeled 'Change Greeting' that changes the greeting message when tapped.
Starter Code
iOS Swift
import UIKit

protocol GreetingService {
    func getGreeting() -> String
}

class GreetingViewController: UIViewController {
    private let greetingService: GreetingService
    private let greetingLabel = UILabel()
    private let changeButton = UIButton(type: .system)

    init(greetingService: GreetingService) {
        self.greetingService = greetingService
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        setupViews()
        // TODO: Display initial greeting
        // TODO: Add action for button tap
    }

    private func setupViews() {
        greetingLabel.translatesAutoresizingMaskIntoConstraints = false
        greetingLabel.textAlignment = .center
        greetingLabel.font = UIFont.systemFont(ofSize: 24)
        view.addSubview(greetingLabel)

        changeButton.translatesAutoresizingMaskIntoConstraints = false
        changeButton.setTitle("Change Greeting", for: .normal)
        view.addSubview(changeButton)

        NSLayoutConstraint.activate([
            greetingLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            greetingLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            changeButton.topAnchor.constraint(equalTo: greetingLabel.bottomAnchor, constant: 20),
            changeButton.centerXAnchor.constraint(equalTo: view.centerXAnchor)
        ])
    }
}
Task 1
Task 2
Task 3
Solution
iOS Swift
import UIKit

protocol GreetingService {
    func getGreeting() -> String
}

class DefaultGreetingService: GreetingService {
    private var toggle = false
    func getGreeting() -> String {
        toggle.toggle()
        return toggle ? "Hello, User!" : "Welcome back!"
    }
}

class GreetingViewController: UIViewController {
    private let greetingService: GreetingService
    private let greetingLabel = UILabel()
    private let changeButton = UIButton(type: .system)

    init(greetingService: GreetingService) {
        self.greetingService = greetingService
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        setupViews()
        greetingLabel.text = greetingService.getGreeting()
        changeButton.addTarget(self, action: #selector(changeGreeting), for: .touchUpInside)
    }

    private func setupViews() {
        greetingLabel.translatesAutoresizingMaskIntoConstraints = false
        greetingLabel.textAlignment = .center
        greetingLabel.font = UIFont.systemFont(ofSize: 24)
        view.addSubview(greetingLabel)

        changeButton.translatesAutoresizingMaskIntoConstraints = false
        changeButton.setTitle("Change Greeting", for: .normal)
        view.addSubview(changeButton)

        NSLayoutConstraint.activate([
            greetingLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            greetingLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            changeButton.topAnchor.constraint(equalTo: greetingLabel.bottomAnchor, constant: 20),
            changeButton.centerXAnchor.constraint(equalTo: view.centerXAnchor)
        ])
    }

    @objc private func changeGreeting() {
        greetingLabel.text = greetingService.getGreeting()
    }
}

We created a GreetingService protocol to define a contract for greeting messages. The DefaultGreetingService class implements this protocol and toggles between two greetings each time getGreeting() is called.

The GreetingViewController receives the service via its initializer, demonstrating dependency injection. This means the controller does not create the service itself but gets it from outside, making the code easier to test and change.

We set the initial greeting in viewDidLoad and update the label when the button is tapped by calling the service again.

Final Result
Completed Screen
-----------------------
| Greeting Screen      |
|---------------------|
| Hello, User!        |
|                     |
| [Change Greeting]   |
-----------------------
When the user taps 'Change Greeting', the greeting text toggles between 'Hello, User!' and 'Welcome back!'
Stretch Goal
Add a second implementation of GreetingService that returns greetings in another language and allow switching between services.
💡 Hint
Create a new class implementing GreetingService with different greetings. Add a segmented control to switch the service instance injected into the view controller.

Practice

(1/5)
1. What is the main purpose of dependency injection in Swift programming?
easy
A. To provide objects with their dependencies from outside rather than creating them inside
B. To make the app run faster by caching data
C. To automatically generate user interfaces
D. To encrypt data before saving

Solution

  1. Step 1: Understand dependency injection concept

    Dependency injection means giving an object the things it needs (dependencies) from outside instead of creating them inside the object.
  2. Step 2: Identify the main purpose

    This approach helps keep code flexible, easier to test, and cleaner by separating concerns.
  3. Final Answer:

    To provide objects with their dependencies from outside rather than creating them inside -> Option A
  4. Quick Check:

    Dependency injection = provide dependencies from outside [OK]
Hint: Dependency injection means giving dependencies from outside [OK]
Common Mistakes:
  • Thinking it speeds up app performance
  • Confusing with UI generation
  • Mixing with data encryption
2. Which of the following is the correct syntax for constructor injection in Swift?
easy
A. class Client { func inject(service: Service) { self.service = service } }
B. class Service { init() { } } class Client { let service: Service; init(service: Service) { self.service = service } }
C. class Client { var service = Service() }
D. class Service { func start() {} }

Solution

  1. Step 1: Identify constructor injection pattern

    Constructor injection means passing dependencies through the initializer (init) of a class.
  2. Step 2: Check which code passes dependency via init

    class Service { init() { } } class Client { let service: Service; init(service: Service) { self.service = service } } shows a Client class receiving a Service instance through its init method and storing it.
  3. Final Answer:

    class Service { init() { } } class Client { let service: Service; init(service: Service) { self.service = service } } -> Option B
  4. Quick Check:

    Constructor injection uses init to pass dependencies [OK]
Hint: Constructor injection passes dependencies via init method [OK]
Common Mistakes:
  • Creating dependencies inside the class instead of passing them
  • Using methods instead of init for injection
  • Not storing the injected dependency
3. Given the code below, what will be printed?
protocol Logger { func log(_ message: String) }
class ConsoleLogger: Logger { func log(_ message: String) { print("Console: \(message)") } }
class Client {
  let logger: Logger
  init(logger: Logger) { self.logger = logger }
  func doWork() { logger.log("Work done") }
}
let client = Client(logger: ConsoleLogger())
client.doWork()
medium
A. No output
B. Work done
C. Console: Work done
D. Logger: Work done

Solution

  1. Step 1: Understand the Logger protocol and ConsoleLogger class

    ConsoleLogger implements Logger and prints messages prefixed with "Console:".
  2. Step 2: Trace the Client usage

    Client is initialized with ConsoleLogger and calls logger.log("Work done"), which prints "Console: Work done".
  3. Final Answer:

    Console: Work done -> Option C
  4. Quick Check:

    Injected ConsoleLogger prints with "Console:" prefix [OK]
Hint: Injected logger prints message with prefix [OK]
Common Mistakes:
  • Ignoring the prefix in print statement
  • Assuming no output because of protocol
  • Confusing method names
4. Identify the error in the following Swift code using dependency injection:
protocol DataService { func fetchData() -> String }
class RealDataService: DataService { func fetchData() -> String { "Real Data" } }
class ViewModel {
  var dataService: DataService
  init() { }
  func load() -> String { dataService.fetchData() }
}
medium
A. dataService is not initialized before use
B. fetchData() should return Int, not String
C. ViewModel should not have an init method
D. RealDataService does not conform to DataService

Solution

  1. Step 1: Check property initialization

    ViewModel has a non-optional property dataService but its init() does not initialize it.
  2. Step 2: Understand Swift initialization rules

    Non-optional properties must be initialized before use; missing initialization causes a compile error.
  3. Final Answer:

    dataService is not initialized before use -> Option A
  4. Quick Check:

    Non-optional properties must be initialized [OK]
Hint: Non-optional properties must be set in init [OK]
Common Mistakes:
  • Assuming default nil for non-optional
  • Confusing return types
  • Ignoring protocol conformance
5. You want to make a Swift class NetworkManager testable by injecting a protocol NetworkService. Which approach best applies dependency injection and allows easy swapping of real and mock services?
hard
A. Create NetworkManager with a fixed real network service inside the class
B. Make NetworkManager inherit from NetworkService and override methods
C. Use global variables to hold the network service instance
D. Define a NetworkService protocol, make NetworkManager accept it via init, and pass either real or mock implementations when creating NetworkManager

Solution

  1. Step 1: Understand testability with dependency injection

    To test NetworkManager easily, inject a protocol so you can swap real and mock implementations.
  2. Step 2: Identify best practice

    Passing NetworkService via init allows NetworkManager to use any implementation, making testing flexible.
  3. Final Answer:

    Define a NetworkService protocol, make NetworkManager accept it via init, and pass either real or mock implementations when creating NetworkManager -> Option D
  4. Quick Check:

    Inject protocol via init for flexible testing [OK]
Hint: Inject protocol via init to swap real/mock easily [OK]
Common Mistakes:
  • Hardcoding real service inside class
  • Using global variables causing tight coupling
  • Misusing inheritance instead of composition
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard