SwiftUI의 종속 주입
@EnvironmentObject
property wrapper 의존 주입
의존 주입, 약칭 DI는 한 대상에 의존하는 다른 대상을 제공하는 실천이지 내부에서 그것을 만드는 것이 아니다.
// Without dependency injection
class Foo {
let bar: Bar = Bar()
}
// With dependecy injection
class Foo {
let bar: Bar
init(bar: Bar) {
self.bar = bar
}
}
DI는 디자인을 더욱 유연하게 하고 코드 honest을 보존하며 프로토콜과 페어링할 때 이중 테스트를 통해 대상의 행동을 테스트할 수 있습니다.의존항 주입의 도전은 구성 요소에 필요한 의존항을 어떻게 제공하는가이다. 수동으로 이를 차원 구조의 모든 조상에게 전달할 필요가 없다.
@EnvironmentObject
과 보기모델 공장은 모두 이를 위해 깨끗한 해결 방안을 제공했다.이 두 가지 방법을 비교하기 위해서, 우리가 도서관 읽기 목록 응용 프로그램을 구축하고 있다고 가정하자.탭 보기는 두 개의 화면이 있습니다. 하나는 도서관의 모든 서적을 표시하고, 다른 하나는 당신의 읽기 목록을 표시합니다.라이브러리 목록에서 제목을 선택하여 세부 정보를 보고 추가 또는 읽기 목록에서 삭제할 수 있습니다.
읽을 목록과 책의 상세한 보기는 읽을 목록 저장소에 접근해야 한다.우리는 그것을
ReadingListController
이라고 부른다.import Combine
class ReadingListController: ObservableObject {
// By publishing the reading list, we can leverage SwiftUI to automatically
// update the UI when a book is added or removed.
//
// For the sake of this example, let's use in-memory storage. In the real
// world, we'd be storing to disk and/or calling a remote API.
@Published private(set) var readingList: [Book] = []
func isBookInList(_ book: Book) -> Bool { ... }
func add(_ book: Book) { ... }
func remove(_ book: Book) { ... }
}
이런 의존성을 주입하는 두 가지 방법을 봅시다.@ 환경 객체
SwiftUI는 상위 뷰 또는 상위 뷰에서 관찰 가능한 객체를 정의하는
@EnvironmentObject
property wrapper을 제공합니다.포장된 ObservableObject
이 변경될 때마다 프레임은 보기를 무효화시켜 다시 그려집니다.@EnvironmentObject
은 SwiftUI 환경에서 가치를 찾기 때문에 의존 항목을 주입할 수 있습니다.이것은 차원 구조 깊은 곳의 보기가 의존항에 접근할 수 있고 그 부모급이 그것을 전달할 필요가 없다는 것을 의미한다.의존항을 환경에 추가하는 방법은 보기에 접근해야 하는 모든 조상에게 [
environmentObject(_:)
](https://developer.apple.com/documentation/swiftui/view/environmentobject(_:%29)) 방법을 호출하는 것입니다. 이것은 맨 위에서 완성하는 것이 가장 좋습니다. App
에서 실현하거나 UIWindowSceneDelegate
에서 SwiftUI와 UIKIT를 혼합하면 됩니다.코드 좀 보여주기;이 예시의 원본 코드 here을 얻을 수 있습니다.
import SwiftUI
@main
struct ReadingListApp: App {
// The interface with the reading list storage.
// This is the only place where we instantiate ReadingListController; no
// singletons or static shared instances needed.
let readingListController = ReadingListController()
var body: some Scene {
WindowGroup {
TabView {
NavigationView {
ToReadList().navigationTitle("To Read 📖")
}
.tabItem { Text("To Read") }
NavigationView {
BookList().navigationTitle("Books 📚")
}
.tabItem { Text("All Books") }
}
// Here we inject the ReadingListController instance in the
// environment
.environmentObject(readingListController)
}
}
}
ReadingListController
에 접근해야 하는 보기는 @EnvironmentObject
을 통해 얻을 수 있습니다.다른 사람은 알 필요가 없다.struct BookList: View {
// Let's skip how to load the library books for the sake of brevity
let books: [Book] = ...
var body: some View {
List(books) { item in
// BookList defines the view where to navigate when a row is
// selected, but notice how it doesn't provide it with a reference
// to a ReadingListController.
NavigationLink(destination: BookDetail(book: item)) {
Text(item.title)
}
}
}
}
struct BookDetail: View {
let book: Book
// Here, we access our injected dependency from the environment
@EnvironmentObject var readingListController: ReadingListController
var body: some View {
VStack {
Text(book.title)
Text(book.author)
if readingListController.isBookInList(book) {
Button(action: { self.readingListController.remove(book) }) {
Text("Remove from reading list")
}
} else {
Button(action: { self.readingListController.add(book) }) {
Text("Add to reading list")
}
}
}
}
}
struct ToReadList: View {
// Here, too, we get our ReadingListController from the environment
@EnvironmentObject var readingListController: ReadingListController
var body: some View {
List(readingListController.readingList) { item in
Text(item.title)
Text(item.author)
}
}
}
일단 @EnvironmentObject
의 작업 원리를 이해하면 위의 코드는 간결하고 이해하기 쉽다.SwiftUI 프레임워크의 내부 구조로 인해 읽을 목록과 책 디테일 화면의 동기화를 유지하기 위해 코드를 작성할 필요가 없습니다.모든 것이 우리를 위해 준비되었다.하지만
environmentObject
으로 전화를 걸지 않거나 의외로 삭제하면 프로그램이 붕괴됩니다.다음 방법은 운행할 때 붕괴될 위험을 없앴다.
뷰 모델 및 뷰 모델 공장
SwiftUI에서 MVVM 모드를 사용하여 각 뷰에 데이터를 표시하고 작업하는 모든 논리를 포함하는 뷰 모델을 제공하는 경우 다음과 같이 종속성을 주입할 수 있습니다.
init
시간에 논리를 통해 보기 모델에서 보기를 구축한다.및 init
에 주입할 수 있다.NavigationLink
의 목표로 사용하든 단추에 텍스트를 표시하든 모든 논리를 보기 모델에 의뢰해야 한다.import SwiftUI
struct BookList: View {
let viewModel: BookListViewModel
var body: some View {
List(viewModel.books) { item in
// The view model tells the view what's the NavigationLink destination
NavigationLink(destination: viewModel.viewForSelectedBook(item)) {
Text(item.title)
}
}
}
}
struct BookDetail: View {
@ObservedObject var viewModel: BookDetailViewModel
var body: some View {
VStack {
Text(viewModel.title)
Text(viewModel.author)
// The view models take care of actioning on the reading list.
// Because of that, the view only need an instance of the view
// model; the ReadingListController dependency is hidden inside it.
Button(action: viewModel.addOrRemoveBook) {
Text(viewModel.addOrRemoveButtonText)
}
}
}
}
이 방법의 코드 예시 here을 찾을 수 있습니다.BookDetail
현재 if-else
조건이 없습니다.보기는 humble입니다.그것은 추가 논리가 필요 없이 보기 모델이 알려준 조작을 실행한다.
보기 모델 자체가 보기를 어떻게 구축하는지 모른다.그들은 초기 시간에 이 지식을 얻기 위해 폐쇄적인 형식으로 요구했다.
BookListViewModel
참조:import Combine
class BookListViewModel: ObservableObject {
let books: [Book]
// When creating the view model, inject the logic to create the detail view
// for a given book.
let viewForSelectedBook: (Book) -> BookDetail
init(books: [Book], viewForSelectedBook: @escaping (Book) -> BookDetail) {
self.books = books
self.viewForSelectedBook = viewForSelectedBook
}
}
class BookDetailViewModel: ObservableObject {
private let book: Book
private let readingListController: ReadingListController
let title: String { book.title }
let author: String { book.author }
@Published var addOrRemoveButtonText: String
init(book: Book, readingListController: ReadingListController) {
self.book = book
self.readingListController = readingListController
// This method is defined in a private extension below to DRY the code
// without having to define a static function that could be accessed
// here when self is not yet available.
addOrRemoveButtonText = readingListController.textForAddOrRemoveButton(for: book)
}
func addOrRemoveBook() {
if readingListController.isBookInList(book) {
readingListController.remove(book)
} else {
readingListController.add(book)
}
addOrRemoveButtonText = readingListController.textForAddOrRemoveButton(for: book)
}
}
private extension ReadingListController {
func textForAddOrRemoveButton(for book: Book) -> String {
isBookInList(book) ? "Remove from reading list" : "Add to reading list"
}
}
수수께끼의 마지막 부분은 집중 위치에서 실제 ReadingListController
의존항을 주입하는 것이다.이 대상은 유일하게 실례화된 ReadingListController
의 대상이 되고 보기 모델을 만들어 의존 관계를 필요로 하는 사람에게 전달할 것이다.다른 대상을 만드는 데만 사용할 수 있는 좋은 이름은 공장입니다. 이것은 클래스가 구성 요소의 실례화를 하위 클래스로 미루는 기능을 없앴음에도 불구하고 factory pattern에 대한 힌트입니다.
class ViewModelFactory {
// Like when using the environment approach, ViewModelFactory is the only
// point where we instantiate ReadingListController; no singletons or
// static shared instances needed.
let readingListController = ReadingListController()
// Once again, let's gloss over how to load the books for the sake of
// brevity.
let books: [Book] = ...
func makeBookListViewModel() -> BookListViewModel {
return BookListViewModel(
books: books,
viewForSelectedBook: { [unowned self] in
BookDetail(viewModel: self.makeBookDetailViewModel(for: $0))
}
)
}
func makeBookDetailViewModel(for book: Book) -> BookDetailViewModel {
return BookDetailViewModel(book: book, readingListController: readingListController)
}
func makeToReadListViewModel() -> ToReadListViewModel {
return ToReadListViewModel(readingListController: readingListController)
}
}
ViewModelFactory
은 모든 보기 모델을 구축했고 각 보기 모델은 init
시간에 보기 구축의 논리를 받아들였기 때문에 이 모델은 의존항을 차원 구조의 각 노드에 전달하는 수요를 없앴다.SwiftUI 응용 프로그램의 맨 위에서
ViewModelFactory
을 호출할 수 있습니다. App
이든 UIWindowSceneDelegate
이든 루트 보기의 보기 모델을 얻을 수 있습니다.import SwiftUI
@main
struct ReadingListApp: App {
let viewModelFactory = ViewModelFactory()
var body: some Scene {
WindowGroup {
TabView {
NavigationView {
ToReadList(viewModel: viewModelFactory.makeToReadListViewModel())
.navigationTitle("To Read 📖")
}
.tabItem { Text("To Read") }
NavigationView {
BookList(viewModel: viewModelFactory.makeBookListViewModel())
.navigationTitle("Books 📚")
}
.tabItem { Text("All Books") }
}
}
}
}
장점과 단점
@EnvironmentObject
은 실행할 때 안전성을 간결성으로 바꾸는 것이 교과서와 같은 빠른 방법이지만, 응용 프로그램을 붕괴시킬 수 있습니다.보기 모형을 사용하여 의존 주입을 하려면 더 많은 작업과 약정이 필요하고 개발자는 이러한 작업과 약정을 준수해야 하지만 운행할 때 안전하다.
실행할 때 안전하다는 보답을 받았을 때, 추가 코드를 작성하는 것은 합리적인 균형인 것 같다.이게 내가 더 좋아하는 방법이야.하지만 저는 SwiftUI에 대해 아직 초보입니다. UIKIT를 사용한 지 여러 해가 지난 후에 형성된 사고방식과 습관이 어느 정도에 제 프레임워크에서의 사고를 방해했는지 알고 싶습니다.
너는 이 두 가지 방식 중 어느 것을 좋아하니?SwiftUI에 의존 항목을 주입하는 다른 방법이 있습니까?나는 너의 편지를 매우 받고 싶다.
아래에 메시지를 남기거나 트위터에 연락하세요.
이것은 내 블로그 mokacoding.com의 교차 게시물이다.Swift, 테스트, 생산성에 대해 자세히 알아보십시오.
Adam Campbell에게 감사드립니다. 이 글의 초기 초고를 검토해 주셔서 감사합니다.
관련 직위
보기 컨트롤러 내비게이션 테스트 방법
지오 로디・ 2019년 4월 17일・ 5분 읽기
#swift
#testing
#ios
Swift의 테스트는 가짜, 가짜, 재고품, 스파이로 배가된다.
지오 로디・ 2019년 2월 15일・ 6분 읽기
#swift
#testing
#tdd
후회하지 않는 책 열 권
지오 로디・ 2019년 3월 27일・ 6분 읽기
#productivity
#books
Reference
이 문제에 관하여(SwiftUI의 종속 주입), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/mokagio/dependency-injection-in-swiftui-5c7f텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)