Monti

Swift Architecture / 스위프트 아키텍쳐 패턴 본문

IOS/Swift

Swift Architecture / 스위프트 아키텍쳐 패턴

montt 2022. 9. 28. 22:28

Swift Architecture

Swift UI Framework


MVC 패턴


Model, View, Controller

MVC 패턴은 Model-View-Controller 패턴의 줄임말로, 이름 그대로 세 가지 계층으로 각 코드의 책임과 역할을 나눈다. 계층은 각각 Model, View, Controller로 나뉜다. iOS에서의 MVC는 보통 View와 Controller가 합쳐진 형태로 되어있어 이를 Massive View Controller라고 부른다.

Swift MVC 패턴

  1. ModelModel에는 대부분 다음과 같은 코드가 포함된다.

    • 데이터로 사용하는 구조체

    • 네트워크 로직 : 네트워크 요청을 하고, 그 결과를 받아오는 기본적인 기능을 담은 로직

    • Persistance 로직 : 메모리에 저장되는 데이터를 로드 및 세이브하는 로직

    • 데이터 파싱 로직 : 외부 혹은 내부에서 가져오는 JSON 과 같은 데이터를 파싱하는 로직

    • Manager 객체 (shared 객체) : 구조체를 만들어두고 필요한 경우에는 어디서든 접근해 사용할 수 있도록 따로 Manager를 만드는 경우

    • Util, Extension, Constant: Color, String의 추가적인 기능, 특정 사이즈에 대한 정보를 만드는 경우

      // 명언생성기 어플
      struct Quote {
        let contents: String
        let name: String
      }
    • Model 예시

  2. Model은 데이터와 관련된 내용을 담고 있다. 그리고 데이터를 관리하는 로직도 포함하고 있다. 네트워크를 통해 받아온 DTO 구조체와 네트워크에 접근하는 로직, 파일로 따로 저장해야하는 Persistance한 데이터를 로드한다던가, 아니면 필요한 구조체를 만드는 경우 해당 내용들을 모두 Model에 포함된다.

  3. ViewView를 작성할 때는 재사용성이 강조된다.

    • UIView를 상속해 만들어진 subclass
    • Core Animation
    • Core Graphics
  4. View에는 다음과 같은 코드들이 포함된다

  5. View는 흔히 말하는 UI이다. 유저들에게 데이터를 보여주고, 어떻게 보여줄지 화면을 구성하는 코드들이 포함되어 있다. 또한, View 계층은 유저와 직접 상호작용을 하는 곳이기 때문에, Controller 계층으로 상호작용을 전달하는 역할을 한다.

  6. Controller

  7. Controller는 앱의 핵심 로직을 담고 있는 계층이다. Controller는 View, Model에 연결되어 그 중간의 역할을 하고있다.

기본적으로 iOS에서의 MVC 디자인 패턴은 Controller 내부에 View가 포함된 경우가 많다. 이를 뷰 컨트롤러 덩어리(Massive View Controller)라고 부른다.

// 명언 생성기 어플
import UIKit

// Controller
class ViewController: UIViewController {
      // View
        @IBOutlet weak var quoteLabel: UILabel!
    @IBOutlet weak var nameLabel: UILabel!

        let quotes = [
        Quote(contents: "죽음을 두려워하는 나머지 삶을 시작조차 못하는 사람이 많다.", name: "벤다이크"),
        Quote(contents: "나는 나 자신을 빼 놓고는 모두 안다.", name: "비용"),
        Quote(contents: "편견이란 실효성이 없는 의견이다.", name: "암브로스 빌"),
        Quote(contents: "분노는 바보들의 가슴속에서만 살아간다.", name: "아인슈타인"),
        Quote(contents: "몇 번이라도 좋다! 이 끔직한 생이여...다시!", name: "니체")
    ]

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    @IBAction func tapQuoteGeneratorButton(_ sender: Any) {
        let random = Int(arc4random_uniform(5))
        let quote = quotes[random]
        self.quoteLabel.text = quote.contents
        self.nameLabel.text = quote.name
    }

}

MVP 패턴


Model, View, Presenter

MVP 패턴은 Model-View-Presenter 패턴의 줄임말이다. MVP패턴은 MVC패턴과는 다르게 Presenter라는 새로운 개념이 있다. Presenter은 ViewController life cycle에 영향을 받지 않는다. Presenter는 Controller의 역할을 맡게 되고 데이터와 상태에 따라 View를 갱신하는 역할을 담당한다.

Swift MVP 패턴

  1. Model

    Model은 MVC 패턴에서의 Model과 같은 데이터와 관련된 내용을 담고 있고. 데이터를 관리하는 로직 등이 포함되어 있다.

     // 도서 리뷰 어플 Model
     struct BookReview: Codable {
         let title: String
         let contents: String
         let imageURL: URL?
     }
  2. Passive View

    iOS의 UIView와 UIViewController가 여기에 속하며 모든 비즈니스 로직을 Presenter에 맡긴다.

     // 도서 리뷰 어플 PassiveView(Controller)
     final class ReviewListViewController: UIViewController {
         private lazy var presenter = ReviewListPresenter(viewController: self)
    
         private lazy var tableView: UITableView = {
             let tableView = UITableView()
             tableView.dataSource = presenter
    
             return tableView
         }()
    
         override func viewDidLoad() {
             super.viewDidLoad()
    
             presenter.viewDidLoad()
    
         }
    
         override func viewWillAppear(_ animated: Bool) {
             super.viewWillAppear(animated)
    
             presenter.viewWillAppear()
         }
     }
    
     // presenter의 프로토콜을 정의 및 presenter의 로직에 따른 view Setup
     extension ReviewListViewController: ReviewListProtocol {
         func setupNavigationBar() {
             navigationItem.title = "도서 리뷰"
             navigationController?.navigationBar.prefersLargeTitles = true
    
             let rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(didTapRightBarButtonItem))
             navigationItem.rightBarButtonItem = rightBarButtonItem
         }
         func setupViews() {
             view.addSubview(tableView)
             tableView.snp.makeConstraints {
                 $0.edges.equalToSuperview()
             }
         }
    
         func presentToReviewWriteViewController() {
             let vc = UINavigationController(rootViewController: ReviewWriteViewController())
             vc.modalPresentationStyle = .fullScreen
             present(vc, animated: true)
         }
    
         func reloadTableView() {
             tableView.reloadData()
             print("최신의 도서리뷰 목록 보여주기")
         }
     }
    
     private extension ReviewListViewController {
         @objc func didTapRightBarButtonItem() {
             presenter.didTapRightBarButtonItem()
         }
     }
  3. Presenter

    Project 내의 모든 비즈니스 로직을 수행한다. 또한 Model의 데이터를 가공해 View에서 보여주기 위한 UI 친화적인 포맷으로 바꾸는 역할을 한다.

    기존의 MVC 패턴이 가지고 있는 장점을 유지한 채 UI 담당부분과 비즈니스 로직 부분을 나누어 모듈화 정도를 심화한다.

     // 도서 리뷰 어플 Presenter
     protocol ReviewListProtocol {
         func setupNavigationBar()
         func setupViews()
         func presentToReviewWriteViewController()
         func reloadTableView()
     }
    
     // 비즈니스 로직
     final class ReviewListPresenter: NSObject {
         private let viewController: ReviewListProtocol
         private let userDefaultsManager: UserDefaultsManagerProtocol
    
         private var review: [BookReview] = []
    
         init(viewController: ReviewListProtocol,
              userDefaultsManager: UserDefaultsManagerProtocol = UserDefaultsManager()
         ) {
             self.viewController = viewController
             self.userDefaultsManager = userDefaultsManager
         }
    
         func viewDidLoad() {
             viewController.setupNavigationBar()
             viewController.setupViews()
         }
    
         func viewWillAppear() {
             review = userDefaultsManager.getReviews()
             viewController.reloadTableView()
         }
    
         func didTapRightBarButtonItem() {
             viewController.presentToReviewWriteViewController()
    
         }
     }
    
     extension ReviewListPresenter: UITableViewDataSource {
         func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
             review.count
         }
    
         func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
             let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil)
             let review = review[indexPath.row]
             cell.textLabel?.text = review.title
             cell.detailTextLabel?.text = review.contents
             cell.imageView?.kf.setImage(with: review.imageURL, placeholder: .none) { _ in
                 cell.setNeedsLayout()
    
             }
             cell.selectionStyle = .none
    
             return cell
         }
    
      }

MVVM 패턴


Model, View, ViewModel

MVVM 패턴은 Model-View-ViewModel 패턴의 줄임말으로, 각 코드를 세가지 계층으로 나뉘는 아키텍처 패턴이다. 양방향 바인딩을 지원한다.

  1. Model

Model은 MVC 패턴에서의 Model과 같은 데이터와 관련된 내용을 담고 있고. 데이터를 관리하는 로직등이 포함되어 있다.

import CoreLocation

struct Center: Hashable, Decodable {
    let id: Int
    let sido: Sido
    let facilityName: String
    let address: String
    let lat: String
    let lng: String
    let centerType: CenterType
    let phoneNumber: String

    enum CenterType: String, Decodable{
        case central = "중앙/권역"
        case local = "지역"
    }

    enum Sido: String, Decodable, CaseIterable, Identifiable {
        case 서울특별시
        case 부산광역시
        case 대구광역시
        case 인천광역시
        ...
        case 전라북도
        case 전라남도
        case 경상북도
        case 경상남도
        case 제주특별자치도

        var id: String {
            return self.rawValue
        }

    }

    var coordinate: CLLocationCoordinate2D{
        return CLLocationCoordinate2D(
            latitude: CLLocationDegrees(self.lat) ?? .zero,
            longitude: CLLocationDegrees(self.lng) ?? .zero
        )        
    }
}
  1. View

MVVM패턴에서의 View는 MVC패턴에서의 View Controller의 역할을 담당한다. 사용자 이벤트를 수신하고 데이터를 표시하는 유저 인터페이스를 책임진다. 하지만 MVC패턴과는 다르게 각각의 View는 자신이 사용할 View Model에서 데이터를 바인딩받아 업데이트를 받는다.

import Foundation
import Combine

class SelectRegionViewModel: ObservableObject {
    @Published var centers = [Center.Sido: [Center]]()
    private var cancellables = Set<AnyCancellable>()

    init(centerNetwork: CenterNetwork = CenterNetwork()) {
        centerNetwork.getCenterList()
            .receive(on: DispatchQueue.main)
            .sink(
                receiveCompletion: {[weak self] in
                guard case .failure(let error) = $0 else { return }
                print(error.localizedDescription)
                self?.centers = [Center.Sido: [Center]]()
                },
                receiveValue: {[weak self] centers in
                self?.centers = Dictionary(grouping: centers){ $0.sido }
                }
            )
            .store(in: &cancellables)
    }
}
  1. ViewModel

ViewModel은 Model에서 변경을 호출하고 Model에 따라 자체를 갱신한다. 갱신된 데이터를 View에 데이터 바인딩을 진행하여 적절하게 View에 표현되는 데이터를 갱신해준다.

  • 바인딩(Binding) : 프로그램에 사용된 구성 요소의 실제 값 또는 프로퍼티를 결정짓는 행위
import SwiftUI

struct SelectRegionView: View {
    @ObservedObject var viewModel = SelectRegionViewModel()

    private var items: [GridItem] {
        Array(repeating: .init(.flexible(minimum: 80)), count: 2)
    }

    var body: some View {
        NavigationView {
            ScrollView {
                LazyVGrid(columns: items, spacing: 20) {
                    ForEach(Center.Sido.allCases, id: \.id) {
                        sido in
                        let centers = viewModel.centers[sido] ?? []
                        NavigationLink(
                            destination: CenterList(centers: centers)) {
                                SelectRegionItem(region: sido, count: centers.count)
                            }
                    }
                }
                .padding()
                .navigationTitle("코로나19 예방접종 센터")
            }
        }
    }
}

참고 : https://gwangyonglee.tistory.com/49

참고 : https://blog.canapio.com/43

'IOS > Swift' 카테고리의 다른 글

Swift UINavigationViewController  (0) 2022.09.28
Swift - UIViewController  (0) 2022.09.28
Swift UIKit 설명  (0) 2022.09.28
Swift 기본 문법 정리  (0) 2022.09.28
Swift - 기본개념  (0) 2022.09.28
Comments