SwiftUI 5.5를 사용하여 아키텍처 청소

Clean Architecture를 사용하면 결합도가 매우 낮고 기술적으로 세부적인 응용 프로그램, 예를 들어 데이터베이스와 프레임워크를 독립적으로 설계할 수 있습니다.이런 방식을 통해 응용 프로그램은 유지보수와 유연한 변경이 쉬워졌다.그것도 본질적으로 테스트할 수 있게 되었다.여기서 나는 나의 깨끗한 체계 구조 프로젝트를 어떻게 구축하는지 보여줄 것이다.이번에는 SwiftUI를 사용하여 iOS todo 애플리케이션을 구축합니다.
프로젝트의 폴더/그룹 구조는 다음과 같습니다.
├── Core
├── Data
├── Domain
└── Presentation
역층부터 시작합시다.
이 층은 프로젝트/응용 프로그램의 기능을 설명합니다.많은 응용 프로그램의 구축과 구조 방식은 폴더 구조만 보고 응용 프로그램의 기능을 이해할 수 없다는 것을 설명해 드리겠습니다.가옥을 비교하는 건축을 사용하면 건축의 평면도와 입면도를 살펴보면 건축의 외관과 기능을 신속하게 확정할 수 있다

마찬가지로, 우리 프로젝트의 역층은 응용 프로그램의 기능을 지정하고 설명해야 한다.이 폴더에서 모델, 메모리 라이브러리 인터페이스, 용례를 보존할 것입니다.
├── Core
├── Data
├── Presentation
└── Domain
    ├── Model
    │   ├── Todo.swift
    │   └── User.swift
    ├── Repository
    │   ├── TodoRepository.swift
    │   └── UserRepository.swift
    └── UseCase
        ├── Todo
        │   ├── GetTodos.swift
        │   ├── GetTodo.swift
        │   ├── DeleteTodo.swift
        │   ├── UpdateTodo.swift
        │   └── CreateTodo.swift
        └── User
            ├── GetUsers.swift
            ├── GetUser.swift
            ├── DeleteUser.swift
            ├── UpdateUser.swift
            └── CreateUser.swift


  • 모델: 모델은 일반적으로 문제와 관련된 진실한 대상을 나타낸다.이 폴더에서, 우리는 보통 클래스를 보류해서 대상을 표시합니다.e, g. 대기사항, 사용자 등

  • Repository: 모든 저장소 인터페이스의 컨테이너입니다.라이브러리는 모델과 관련된 모든 작업을 저장하는 중심 위치입니다.이 예에서는 Todo 저장소 인터페이스에서 저장소 방법에 대해 설명합니다.실제 저장소는 데이터 층에 저장됩니다.

  • 용례: 프로그램의 모든 기능을 보여 주는 용기입니다.e, g 수령, 삭제, 생성, 업데이트
  • 표시층은 응용 프로그램이 외부 세계와 어떻게 상호작용하는지 이해하기 위해 소비자와 관련된 모든 코드를 보류할 것이다.레이어는 WebForms, 명령줄 인터페이스, API 끝점 등이 될 수 있음을 나타냅니다. 이 예에서는 TODO 목록과 함께 제공된 뷰 모델의 화면이 됩니다.
    ├── Core
    ├── Data
    ├── Domain
    └── Presentation
        └── Todo
            └── TodoList
                ├── TodoListViewModel.swift
                └── TodoListView.swift
    
    데이터 계층은 외부 종속과 관련된 모든 코드를 유지하여 이러한 코드가 어떻게 구현되는지 확인합니다.
    ├── Core
    ├── Domain
    ├── Presentation
    ├── Data
        ├── Repository
        │   ├── TodoRepositoryImpl.swift
        │   ├── TodoAPIDataSourceImpl.swift
        │   └── TodoDBDataSourceImpl.swift
        └── DataSource
            ├── API
            │   ├── TodoAPIDataSource.swift
            │   └── Entity
            │       ├── TodoAPIEntity.swift
            │       └── UserAPIEntity.swift
            └── DB
                ├── TodoDBDataSource.swift
                └── Entity
                    ├── TodoDBEntity.swift
                    └── UserDBEntity.swift
    

  • 저장소: 저장소 구현

  • 데이터 원본: 모든 데이터 원본 인터페이스와 실체.실체는 데이터베이스에 기록으로 저장된 역 대상의 단일 실례를 나타낸다.DB 테이블 또는 API 끝점에서 열로 표시되는 속성이 있습니다.우리는 데이터가 외부 데이터 원본에서 어떻게 모델링되는지 제어할 수 없기 때문에 이러한 실체는 실현 과정에서 실체에서 역 모델로 비추어야 한다
  • 마지막으로 핵심 층은 모든 층에서 통용되는 구성 요소를 보존합니다. 예를 들어 상수, 설정, 의존항 주입 등입니다. (저희는 소개하지 않겠습니다.)
    우리의 첫 번째 임무는 항상 역 모델과 데이터 실체로부터 시작된다
    struct Todo: Identifiable {
        let id: Int
        let title: String
        let isCompleted: Bool
    }
    
    목록 보기에 이 항목들을 표시하려면 식별성이 필요합니다.
    이제 토도 실체를 만들어 보도록 하겠습니다.
    
    struct TodoEntity: Codable {
        let id: Int
        let title: String
        let completed: Bool
    }
    
    이제 TodoDatasource에 대한 인터페이스 (프로토콜) 를 작성합시다.
    protocol TodoDataSource{    
        func getTodos() async throws -> [Todo]    
    }
    
    Dell은 ToDoapImpl이라는 이름의 프로토콜을 작성할 충분한 리소스를 보유하고 있습니다.
    enum APIServiceError: Error{
        case badUrl, requestError, decodingError, statusNotOK
    }
    
    struct TodoAPIImpl: TodoDataSource{
    
    
        func getTodos() async throws -> [Todo] {
    
            guard let url = URL(string:  "\(Constants.BASE_URL)/todos") else{
                throw APIServiceError.badUrl
            }
    
            guard let (data, response) = try? await URLSession.shared.data(from: url) else{
                throw APIServiceError.requestError
            }
    
            guard let response = response as? HTTPURLResponse, response.statusCode == 200 else{
                throw APIServiceError.statusNotOK
            }
    
            guard let result = try? JSONDecoder().decode([TodoEntity].self, from: data) else {
                throw APIServiceError.decodingError
            }
    
            return result.map({ todoEntity in
                Todo(
                    id: todoEntity.id,
                    title: todoEntity.title,
                    isCompleted: todoEntity.completed
                )
            })
        }
    }
    
    주의: 이 저장소의 getTodos 함수는 처리해야 할 사항 목록을 되돌려줍니다.따라서 TodoEntity->Todo:
    ToDoRespository Impl을 작성하기 전에 도메인 계층에서 프로토콜을 작성합니다.
    protocol TodoRepository{ 
        func getTodos() async throws -> [Todo] 
    }
    
    struct TodoRepositoryImpl: TodoRepository{ 
        var api: TodoAPI 
        func getTodos() async throws -> [Todo] {
            let _todos =  try await api.getTodos()
            return _todos
        }
    }
    
    이제 Todo 저장소가 생겨서 GetTodos 용례를 작성할 수 있습니다
    enum UseCaseError: Error{
        case networkError, decodingError
    }
    
    struct GetTodosUseCase{
        var repo: TodoRepository
    
        func execute() async -> Result<[Todo], UseCaseError>{
            do{
                let todos = try await repo.getTodos()
                return .success(todos)
            }catch(let error){
                switch(error){
                case APIServiceError.decodingError:
                    return .failure(.decodingError)
                default:
                    return .failure(.networkError)
                }
            }
        }
    }
    
    그리고 우리는 프레젠테이션 원고의 보기 모형과 보기를 작성할 수 있다
    @MainActor
    class TodoListViewModel: ObservableObject {
    
        var getTodosUseCase = GetTodosUseCase(repo: TodoRepositoryImpl(api: TodoAPIImpl()))
        @Published var todos: [Todo] = []
        @Published var errorMessage = ""
        @Published var hasError = false
    
        func getTodos() async {
            errorMessage = ""
            let result = await getTodosUseCase.execute()
            switch result{
            case .success(let todos):
                self.todos = todos
            case .failure(let error):
                self.todos = []
                errorMessage = error.localizedDescription
                hasError = true
            }
        }
    }
    
    주의: 보기 모델 클래스에 @MainActor 속성을 사용합니다. 이 함수를 주 라인에서 실행해야 하기 때문입니다. 주 라인은 하나의 예제 참여자이며, 실행기는 주 스케줄링 대기열에 해당합니다.
    struct TodoListView: View {
        @StateObject var vm = TodoListViewModel()
    
    
        fileprivate func listRow(_ todo: Todo) -> some View {
            HStack{
                Image(systemName: todo.isCompleted ? "checkmark.circle": "circle")
                    .foregroundColor(todo.isCompleted ? .green : .red)
                Text("\(todo.title)")
            }
        }
    
        fileprivate func TodoList() -> some View {
            List {
                ForEach(vm.todos){ item in
                    listRow(item)
                }
            }
            .navigationTitle("Todo List")
            .task {
               await vm.getTodos()
            }
            .alert("Error", isPresented: $vm.hasError) {
            } message: {
                Text(vm.errorMessage)
            }
        }
    
        var body: some View {
           TodoList()
        }
    }
    
    

    따라서 다음과 같이 요약할 수 있습니다.

    좋은 웹페이지 즐겨찾기