Feature Modules in iOS
In modern iOS development, organizing code into feature modules can greatly enhance the reusability and maintainability of your app. This article will guide you through creating feature modules using a simple example: FlowerList and FlowerDetails features. We will explore the main layers in an iOS project: View, ViewModel, Use Case, Repository, and Service, and implement async/await for service calls.
The whole source code is available on Github.
Main Layers in iOS Development Projects
- View: Handles the UI components and user interactions.
- ViewModel: Acts as an intermediary between the View and the Use Case, managing UI-related data.
- Use Case: Encapsulates the business logic of a particular feature.
- Repository: Manages data operations and acts as a single source of truth.
- Service: Handles network or database operations.
Shared data manipulation layer
- FlowerRepository
import Foundation
protocol FlowerRepository {
func getFlowers() async -> [Flower]
func getFlowerDetails(id: Int) async -> Flower
}
class FlowerRepositoryImpl: FlowerRepository {
private let flowerService: FlowerService
init(flowerService: FlowerService) {
self.flowerService = flowerService
}
func getFlowers() async -> [Flower] {
await flowerService.fetchFlowers()
}
func getFlowerDetails(id: Int) async -> Flower {
await flowerService.fetchFlowerDetails(id: id)
}
}
- FlowerService Protocol and Implementation
protocol FlowerService {
func fetchFlowers() async -> [Flower]
func fetchFlowerDetails(id: Int) async -> Flower
}
class FlowerServiceImpl: FlowerService {
func fetchFlowers() async -> [Flower] {
/// Simulating network call
[Flower(id: 1, name: "Rose"), Flower(id: 2, name: "Tulip")]
}
func fetchFlowerDetails(id: Int) async -> Flower {
/// Simulating network call
Flower(id: id, name: "Random flower", description: "A beautiful flower")
}
}
Creating Feature Modules
FlowerList Feature
- FlowerListView
import SwiftUI
struct FlowerListView: View {
@StateObject var viewModel: FlowerListViewModel
var body: some View {
NavigationStack {
List(viewModel.flowers) { flower in
NavigationLink {
Assembler.default.flowerDetailsView(id: flower.id)
} label: {
Text(flower.name)
}
}
.onAppear {
Task {
await viewModel.fetchFlowers()
}
}
}
}
}
- FlowerListViewModel
import Combine
class FlowerListViewModel: ObservableObject {
@Published var flowers: [Flower] = []
private let flowerListUseCase: FlowerListUseCase
init(flowerListUseCase: FlowerListUseCase) {
self.flowerListUseCase = flowerListUseCase
}
@MainActor
func fetchFlowers() async {
flowers = await flowerListUseCase.getFlowers()
}
}
- FlowerListUseCase
import Foundation
protocol FlowerListUseCase {
func getFlowers() async -> [Flower]
}
class FlowerListUseCaseImpl: FlowerListUseCase {
private let flowerRepository: FlowerRepository
init(flowerRepository: FlowerRepository) {
self.flowerRepository = flowerRepository
}
func getFlowers() async -> [Flower] {
await flowerRepository.getFlowers()
}
}
FlowerDetails Feature
- FlowerDetailsView
import SwiftUI
struct FlowerDetailsView: View {
@StateObject var viewModel: FlowerDetailsViewModel
let id: Int
var body: some View {
VStack {
if let flower = viewModel.flower {
Text("\(flower.id)")
Text(flower.name)
Text(flower.description ?? "")
}
}
.onAppear {
Task {
await viewModel.fetchFlowerDetails(id: id)
}
}
}
}
- FlowerDetailsViewModel
import Combine
class FlowerDetailsViewModel: ObservableObject {
@Published var flower: Flower?
private let flowerDetailsUseCase: FlowerDetailsUseCase
init(flowerDetailsUseCase: FlowerDetailsUseCase) {
self.flowerDetailsUseCase = flowerDetailsUseCase
}
@MainActor
func fetchFlowerDetails(id: Int) async {
flower = await flowerDetailsUseCase.getFlowerDetails(id: id)
}
}
- FlowerDetailsUseCase
protocol FlowerDetailsUseCase {
func getFlowerDetails(id: Int) async -> Flower
}
class FlowerDetailsUseCaseImpl: FlowerDetailsUseCase {
private let flowerRepository: FlowerRepository
init(flowerRepository: FlowerRepository) {
self.flowerRepository = flowerRepository
}
func getFlowerDetails(id: Int) async -> Flower {
await flowerRepository.getFlowerDetails(id: id)
}
}
Conclusion
By organizing your iOS project into feature modules with distinct layers (View, ViewModel, Use Case, Repository, and Service), you create a clean and maintainable codebase. The FlowerList and FlowerDetails features demonstrate how to keep related business logic within their respective modules, ensuring reusability and separation of concerns. Using async/await for service calls modernizes the code, making it more efficient and easier to read.
Top comments (0)