iOS

[iOS] MVC 패턴이란?

itisjustK 2022. 10. 9. 19:36

✨ MVC의 요소

MVC = Model + View + Controller

  • Model : 앱의 데이터, 비즈니스 로직 (데이터를 관리하는) 포함
  • View : 유저가 보는 UI
  • Controller : Model과 View를 이어주는 중간다리 역할 (Middleman)
    - 해당 View마다 하나씩 붙어서 그 View에 맞는 로직 포함하기 때문에 재사용성은 View보다 훨씬 떨어진다.
    - ViewController 파일에 View, Controller 각각을 담당하는 코드들이 같이 들어가있다. (밀접해있다.)
    - 하지만 스토리보드 없이 코드베이스로 해서 View, Controller 코드 분리할 수 있다.

 

 MVC 개요

 

 MVC 요소들이 서로 연락하는 방법

Model과 View는 서로 직접 연락할 수 없다. Controller 통해서 연락하는 사이. 구체적으로 어떤 방식으로 진행되냐?

 

  • View -> Controller 연락하는 방법

(*정확히 묘사하자면 Controller가 View의 연락 받는 방법)

 

1) Controller에 View가 하는 action에 대한 target을 만들어둔다. (set target which View did action to in Controller)

View에서 유저의 action이 발생하면 Controller에 있는 target이 이를 받아들이고 작업을 수행한다

 

2) delegate 패턴의 delegate & datasource 이용해서 Controller에게 어떤 작업 수행해야 하는지 알려준다.

ex) UITableView의 UITableViewDelegate, UITableViewDataSource

 

 

  • Model -> Controller 연락하는 방법

Model은 Observer 패턴Notification, KVO (Key Value Observation)을 통해 Controller에게 알린다.

Notification과 Observer는 Model의 publisher(객체)가 진행하던 작업이 끝나면 자신들을 구독중인 subscriber에게 신호를 보낸다.

 

 

 

MVC 패턴의 장단점

  • 장점
    • 개발 속도가 빠르다.
    • 규모가 작은 프로젝트에 용이하다.
  • 단점
    • View와 Controller가 너무 밀접하게 붙어있다. 
    • 대부분의 코드가 Controller에 밀집될 수 있다.
    • 규모가 커지면 유지보수가 힘들다.

 

 MVC 패턴 예시 코드

❗️View (스토리 보드)

❗️Controller

  • LoginViewController : 로그인 View 로직을 관리하는 Controller
  • HomeViewController : 홈 View 로직을 관리하는 Controller
  • NetworkService : Model과 소통하는 Controller
// LoginViewController.swift

import UIKit

class LoginViewController: UIViewController {

    @IBOutlet weak var emailField: UITextField!
    @IBOutlet weak var passwordField: UITextField!
    @IBOutlet weak var loginBtn: UIButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        emailField.addTarget(self, action: #selector(self.validateFields), for: .editingChanged)
        passwordField.addTarget(self, action: #selector(self.validateFields), for: .editingChanged)
        
    }
    
    @objc private func validateFields() {
        loginBtn.isEnabled = emailField.hasText && passwordField.hasText
    }
    
    @IBAction func loginBtnClicked(_ sender: UIButton) {
        NetworkService.shared.login(email: emailField.text!, password: passwordField.text!) { success in
            if success {
                self.goToHomePage()
            } else {
                print("Invalid credentials")
            }
        }
    }
    
    private func goToHomePage() {
        let controller = storyboard?.instantiateViewController(withIdentifier: "HomeViewController") as! HomeViewController
        present(controller, animated: true, completion: nil)
    }
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.view.endEditing(true)
    }
}
// HomeViewController.swift

import UIKit

class HomeViewController: UIViewController {

    @IBOutlet weak var welcomeLbl: UILabel!
    
    private var user: User!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        user = NetworkService.shared.getUser()
        welcomeUser()
    }

    private func welcomeUser() {
        welcomeLbl.text = "Hello, \(user.firstName) \(user.lastName)"
    }
}
// NetworkService.swift

import Foundation

class NetworkService {
    
    // 싱글톤 개념 ?!
    static let shared = NetworkService()
    
    private init() {}
    
    private var user: User?
    
    // escaping 탈출 클로저 ?!
    func login(email: String, password: String, completion: @escaping (Bool) -> Void) {
        DispatchQueue.global().async {
            sleep(2)
            DispatchQueue.main.async {
                if email == "test@test.com" && password == "password" {
                    self.user = User(firstName: "K", lastName: "Park", email: "test@test.com", age: 27, location: Location(lat: 3.14, lng: 2.1342))
                    completion(true)
                } else {
                    self.user = nil
                    completion(false)
                }
            }
        }
    }
    
    func getUser() -> User {
        return user!
    }
}

 

 

❗️Model

// User.swift

import Foundation

struct User {
    let firstName, lastName, email: String
    let age: Int
    let location: Location
}

// nested model : User > Location
struct Location {
    let lat: Double
    let lng: Double
}

 

 

 

 예시 코드 결과