swift-dependencies 깃허브 주소 바로가기
DI를 하기 위해서 init을 통해 넘겨서 화면바뀌면 push하며 같이 VM를 넘겨 왔다. 하지만 화면의 수가 많아지고 더 효율적으로 관리해보고 싶어서 해당 라이브러리를 쓰기로 결정했다.swift-dependencies사용법을 다른 학습 없이 사용할 수 있도록 최대한 자세히 작성하려고 한다.NetworkManger를 특정 VM에서 사용하면 가장 단순하게 한다면 이런식으로 하게 된다class NetworkManager {
func fetch() { ... }
}
class ViewModel {
let NetworkManager = NetworkManager() // 직접 생성 (문제)
}
NetworkManager프로토콜을 채택한 객체는 어떤것이든 쓰일 수 있도록 바꾸면 교체와 테스트가 더 용이하다.protocol NetworkManaging {
func fetch()
}
class NetworkManager: NetworkManaging {
func fetch() { ... }
}
class ViewModel {
private let networkManager: NetworkManaging
init(networkManager: NetworkManaging) {
self.networkManager = networkManager
}
}
class ViewController: UIViewController {
private let viewModel: ViewModel // 뷰모델 선언
init() { // VC가 만들어질 때 받아옴
let networkManager = NetworkManager()
self.viewModel = ViewModel(networkManager: networkManager)
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
feature가 많아질수록 매번 생성자 파라미터로 전달해야하는 번거로움이 생긴다.DependencyKey에 liveValue를 정의하고, 그걸 DependencyValues에 연결해서 어디서든 꺼내 쓰는 구조!!struct로 감싸서 등록하는 패턴
- 🤔 클라이언트??
- 서버나 기능을 요청해서 사용하는 쪽
- 이 기능을 사용하는 입장(
ViewModel등)에서 보는API묶음
import Dependencies
struct NetworkClient {
var fetch: () -> Void
}
DependencyKey를 채택해서 liveValue 등록하기
liveValue?
- 실제 앱에서 사용하는 진짜 구현
DependencyKey를 채택해서 실제 앱에서 쓸 기본값을 등록하는 단계이다. extension NetworkClient: DependencyKey {
static let liveValue = NetworkClient(
fetch: {
print("real fetch") // 이해를 돕기위한 코드
}
)
}
DependencyKey에 liveValue를 정의하는 단계DependencyValues에 key 추가DependencyValues라는 저장소에 내가 만든 의존성을 등록하는 단계extension DependencyValues {
var networkClient: NetworkClient {
get { self[NetworkClient.self] }
set { self[NetworkClient.self] = newValue }
}
}
DependencyValues라는 저장소에 networkClient라는 접근 방법을 추가하는 것DependencyKey,DependencyValues를 보면 알겠지만 내부적으로 딕셔너리와 작동원리는 비슷한 구조get { self[NetworkClient.self] }
NetworkClient.self이게 키 역할set { self[NetworkClient.self] = newValue }
[String: Any]이렇게 저장한다면 타입이 안정해져서 의도하지 않은 결과가 나올 수 있음ViewModel에서 사용init(networkManager:)과 같은 코드는 사라짐import Dependencies
final class ViewModel {
@Dependency(\.networkClient) private var networkClient
func load() {
networkClient.fetch()
}
}
private var networkClient는 이제 위에 정의해놓은 networkClient를 통해서 가져오게 됨let networkManager = NetworkManager()
let viewModel = ViewModel(networkManager: networkManager)
let viewModel = ViewModel()
viewModel.load()
live 값 등록하기" 에서 정의한 liveValue를 사용하게 됨withDependencies로 특정 범위에서 의존성을 override하는 방식import Dependencies
let viewModel = withDependencies {
$0.networkClient = NetworkClient(
fetch: {
print("mock fetch")
}
)
} operation: {
ViewModel()
}
viewModel.load()
withDependencies {
$0.networkClient.fetch = {
print("mock fetch")
}
} operation: {
let viewModel = ViewModel()
viewModel.load()
}
@Dependency(\.networkClient) var network
var network: NetworkClient {
get { DependencyValues.current.networkClient }
}
import Dependencies
final class ViewModel {
@Dependency(\.networkClient) private var networkClient
func load() {
networkClient.fetch()
}
}
class ViewModel {
let network: NetworkClient
init() {
self.network = DependencyValues.current.networkClient
}
}
- Client 정의 (기능 정의)
- DependencyKey로 기본값 등록
- DependencyValues에 연결
- @Dependency로 꺼내 사용
import Dependencies
import Foundation
// 1. 의존성 타입 만들기
struct NetworkClient {
var load: @Sendable () throws -> Response
}
// 2. `DependencyKey`를 채택해서 `liveValue` 등록하기
extension NetworkClient: DependencyKey {
static let liveValue = Self(
load: {
try DataService().load()
}
)
// 테스트 할 때 넣을 코드
static let testValue = Self(
loadMenu: unimplemented("\(Self.self).loadMenu")
)
}
// 3. `DependencyValues`에 `key` 추가
extension DependencyValues {
var networkClient: NetworkClient {
get { self[CoffeeMenuClient.self] }
set { self[CoffeeMenuClient.self] = newValue }
}
}
import Dependencies
final class ViewModel {
@Dependency(\.networkClient) private var networkClient
func load() {
networkClient.fetch()
}
}