Dependency Injection
1. Tại sao cần DI?
// ❌ Without DI - hard to test
class ViewModel {
let service = APIService() // Coupled
}
// ✅ With DI - testable
class ViewModel {
let service: APIServiceProtocol
init(service: APIServiceProtocol) {
self.service = service
}
}2. Protocol-based DI
protocol UserService {
func getUsers() async throws -> [User]
}
class RealUserService: UserService {
func getUsers() async throws -> [User] {
// API call
}
}
class MockUserService: UserService {
func getUsers() async throws -> [User] {
[User(id: 1, name: "Test")]
}
}3. Environment Object
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(UserService())
.environmentObject(AuthService())
}
}
}
struct SomeView: View {
@EnvironmentObject var userService: UserService
}4. Dependency Container
class DependencyContainer {
static let shared = DependencyContainer()
lazy var apiClient: APIClient = {
APIClient(baseURL: "https://api.example.com")
}()
lazy var userRepository: UserRepository = {
UserRepositoryImpl(apiClient: apiClient)
}()
func makeUserListViewModel() -> UserListViewModel {
UserListViewModel(repository: userRepository)
}
}
// Usage
let viewModel = DependencyContainer.shared.makeUserListViewModel()5. Testing
class UserViewModelTests: XCTestCase {
func testLoadUsers() async {
let mockService = MockUserService()
let viewModel = UserListViewModel(service: mockService)
await viewModel.loadUsers()
XCTAssertEqual(viewModel.users.count, 1)
}
}📝 Benefits
- Loosely coupled code
- Easy to test with mocks
- Flexible configuration
- Clear dependencies
Last updated on