SwiftUI의 종속 주입

33538 단어 swiftuiswift
SwiftUI에는 다른 종속 주입 방법이 있습니다.이 게시물에서는 다음 두 가지를 소개합니다.
  • Using the @EnvironmentObject property wrapper
  • Using a View Model Factory
  • 의존 주입


    의존 주입, 약칭 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에게 감사드립니다. 이 글의 초기 초고를 검토해 주셔서 감사합니다.

    관련 직위







    좋은 웹페이지 즐겨찾기