[ios] SwiftUI-EnvironmentObject를 View Model에 전달하는 방법?
뷰뿐만 아니라 뷰 모델로 액세스 할 수있는 EnvironmentObject를 만들려고합니다.
Environment 객체는 응용 프로그램 세션 데이터 (예 : logsIn, 액세스 토큰 등)를 추적합니다.이 데이터는 API를 호출하여이 EnvironmentObjects에서 데이터를 전달할 수 있도록보기 모델 (또는 필요한 경우 서비스 클래스)로 전달됩니다.
세션 객체를 뷰에서 뷰 모델 클래스의 초기 자로 전달하려고 시도했지만 오류가 발생했습니다.
SwiftUI를 사용하여 EnvironmentObject를 뷰 모델에 액세스 / 전달할 수 있습니까?
테스트 프로젝트 링크 참조 : https://gofile.io/?c=vgHLVx
답변
ViewModel이 없도록 선택합니다. (아마도 새로운 패턴의 시간?)
프로젝트와 RootView
일부 자식 뷰를 설정했습니다. 내가 설정 내를 RootView
A를 App
EnvironmentObject 같은 객체입니다. ViewModel은 모델에 액세스하는 대신 모든 뷰가 App의 클래스에 액세스합니다. ViewModel은 레이아웃을 결정하는 대신 레이아웃을 결정합니다. 실제로 몇 가지 응용 프로그램 에서이 작업을 수행 한 결과 내 견해가 작고 구체적으로 유지됩니다. 지나친 단순화로 :
class App {
@Published var user = User()
let networkManager: NetworkManagerProtocol
lazy var userService = UserService(networkManager: networkManager)
init(networkManager: NetworkManagerProtocol) {
self.networkManager = networkManager
}
convenience init() {
self.init(networkManager: NetworkManager())
}
}
struct RootView {
@EnvironmentObject var app: App
var body: some View {
if !app.user.isLoggedIn {
LoginView()
} else {
HomeView()
}
}
}
struct HomeView: View {
@EnvironmentObject var app: App
var body: some View {
VStack {
Text("User name: \(app.user.name)")
Button(action: { app.userService.logout() }) {
Text("Logout")
}
}
}
}
미리보기에서 MockApp
의 하위 클래스 인를 초기화합니다 App
. MockApp은 Mocked 객체로 지정된 이니셜 라이저를 초기화합니다. 여기서는 UserService를 조롱 할 필요는 없지만 데이터 소스 (예 : NetworkManagerProtocol)는 그렇지 않습니다.
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
Group {
HomeView()
.environmentObject(MockApp() as App) // <- This is needed for EnvironmentObject to treat the MockApp as an App Type
}
}
}
답변
해서는 안됩니다. SwiftUI가 MVVM에서 가장 잘 작동한다는 것은 일반적인 오해입니다.
SwfitUI에는 MVVM이 없습니다. 당신은 당신이 직사각형을 밀어 넣을 수 있는지 묻는
삼각형 모양에 맞습니다. 맞지 않을 것입니다.
몇 가지 사실부터 시작하여 단계별로 작업 해 봅시다.
-
ViewModel은 MVVM의 모델입니다.
-
MVVM은 고려할 가치 유형 (예 : Java에는 해당 사항 없음)을 취하지 않습니다.
-
값 유형 모델 (상태가없는 모델)은 참조보다 안전한 것으로 간주됩니다
불변성의 의미에서 형식 모델 (상태가있는 모델).
이제 MVVM에서는 변경 될 때마다 모델을 설정해야합니다.
미리 결정된 방식으로보기를 업데이트합니다. 이것을 바인딩이라고합니다.
구속력이 없다면, 당신은 예를 들어 우려를 분리 할 수 없습니다. 리팩토링
모델 및 관련 상태를보고 별도의 상태로 유지합니다.
대부분의 iOS MVVM 개발자가 실패하는 두 가지 사항은 다음과 같습니다.
-
iOS는 전통적인 자바 의미에서 “바인딩”메커니즘이 없습니다.
일부는 바인딩을 무시하고 객체 ViewModel을 호출한다고 생각합니다.
자동적으로 모든 것을 해결합니다. 일부는 KVO 기반 Rx를 소개하고
MVVM이 일을 더 단순하게 만들어야 할 때 모든 것을 복잡하게 만듭니다.
-
상태가있는 모델은 너무 위험합니다
MVVM이 ViewModel에 너무 중점을 두었 기 때문에 상태 관리에 너무 적은 부분을
그리고 통제 관리의 일반적인 원칙; 대부분의 개발자들은
뷰를 업데이트하는 데 사용되는 상태의 모델을 재사용 할 수 있고
테스트 가능 .
이것이 바로 Swift가 먼저 가치 유형을 소개하는 이유입니다. 없는 모델
상태.
이제 귀하의 질문에 : ViewModel이 EnvironmentObject (EO)에 액세스 할 수 있는지 묻습니다.
해서는 안됩니다. SwiftUI에서 View를 따르는 모델은 자동으로
EO를 참조하십시오. 예를 들어;
struct Model: View {
@EnvironmentObject state: State
// automatic binding in body
var body: some View {...}
}
사람들이 소형 SDK가 어떻게 설계되어 있는지 이해할 수 있기를 바랍니다.
SwiftUI에서 MVVM은 자동 입니다. 별도의 ViewModel 객체가 필요하지 않습니다.
EO 참조가 전달되어야하는 뷰에 수동으로 바인딩됩니다.
위 코드 는 MVVM입니다. 예를 들어; 볼 바인딩이있는 모델
그러나 모델은 값 유형이므로 모델과 상태를 리팩토링하는 대신
모델을 볼 때 제어를 리팩토링합니다 (예 : 프로토콜 확장).
이것은 디자인 패턴을 언어 기능에 맞추는 것이 아니라 공식 SDK입니다.
그것을 시행. 형식보다 실질 우선.
솔루션을 살펴보면 기본적으로 전역 인 싱글 톤을 사용해야합니다. 당신
보호없이 세계 어디에서나 액세스하는 것이 얼마나 위험한지 알아야합니다
불변성, 참조 타입 모델을 사용해야하기 때문에 필요 없습니다!
TL; DR
SwiftUI에서는 Java 방식으로 MVVM을 수행하지 않습니다. 그리고 그것을하는 Swift-y 방법은 필요하지 않습니다.
이를 위해 이미 내장되어 있습니다.
이것이 인기있는 질문처럼 보였으므로 더 많은 개발자가 이것을 보길 바랍니다.
답변
아래는 저에게 맞는 접근법을 제공했습니다. Xcode 11.1로 시작된 많은 솔루션으로 테스트되었습니다.
이 문제는 EnvironmentObject가 주입되는 방식, 일반적인 스키마에서 비롯되었습니다.
SomeView().environmentObject(SomeEO())
즉, 처음 생성 된 뷰, 두 번째 생성 된 환경 개체, 뷰에 주입 된 세 번째 환경 개체
따라서 뷰 생성자에서 뷰 모델을 생성 / 설정 해야하는 경우 환경 객체가 아직 존재하지 않습니다.
솔루션 : 모든 것을 분리하고 명시 적 의존성 주입을 사용하십시오.
코드에서 보이는 방법은 다음과 같습니다 (일반 스키마).
// somewhere, say, in SceneDelegate
let someEO = SomeEO() // create environment object
let someVM = SomeVM(eo: someEO) // create view model
let someView = SomeView(vm: someVM) // create view
.environmentObject(someEO)
ViewModel과 EnvironmentObject는 의도적으로 참조 유형 (실제로 ObservableObject
)이므로 참조 및 포인터 만 전달합니다.
class SomeEO: ObservableObject {
}
class BaseVM: ObservableObject {
let eo: SomeEO
init(eo: SomeEO) {
self.eo = eo
}
}
class SomeVM: BaseVM {
}
class ChildVM: BaseVM {
}
struct SomeView: View {
@EnvironmentObject var eo: SomeEO
@ObservedObject var vm: SomeVM
init(vm: SomeVM) {
self.vm = vm
}
var body: some View {
// environment object will be injected automatically if declared inside ChildView
ChildView(vm: ChildVM(eo: self.eo))
}
}
struct ChildView: View {
@EnvironmentObject var eo: SomeEO
@ObservedObject var vm: ChildVM
init(vm: ChildVM) {
self.vm = vm
}
var body: some View {
Text("Just demo stub")
}
}