ProjectWithGitAPI
안녕하세요!
이전에 혼자 Git API를 이용한 작은 프로젝트를 진행했습니다.
당시에는 MVVM 아키텍처로 구현하려고 했는데....
진행하다 보니 스스로 아키텍처 패턴에 대한 이해가 낮은 게 느껴지더라구요.
그래서 아키텍처 패턴을 다시 학습하고!
동일한 기능을 다양한 아키텍처 패턴으로 구현하는 프로젝트를 진행하려고 합니다.
(최대한 아키텍처 패턴을 준수하도록 신경 쓰면서 코드를 짰습니다.)
해당 프로젝트가 규모는 작아도 Rest API, 네트워킹, 이미지 Caching, 페이징 처리,
외부 라이브러리 사용 등등 배웠던 기술들은 거의 다 적용되었습니다.
그래서 ProjectWithGitAPI 라는 이름으로 아키텍처 패턴을 공부하기 위한 프로젝트로 채택하게 되었습니다.
MVC
MVC 패턴을 적용하려면 MVC 패턴을 이해해야겠죠?
우선 제가 정리한 UIKit환경의 MVC 패턴의 그림이고 기본 개념은 다음과 같습니다.
- Model은 데이터와 비즈니스 로직을,
- View는 UI를,
- Controller는 View와 Model의 중간 역할과 프레제테이션 로직을 관리하는 역할도 합니다.
위 내용을 토대로 추가 설명을 하겠습니다.
UIKit환경에서는 그림처럼 View와 컨트롤러가 밀접하게 연관이 되어 있습니다.
왜 이렇게 설계했을까요?
개인적인 생각을 말해보자면...
MVC 패턴의 역사는 몹시 길고 개발환경에 따라 다양한 형태의 MVC 패턴이 존재했습니다.
MVC 패턴이 등장한 초기에는 View는 출력(Output)을 Controller는 입력(Input)을 받도록
역할이 구분이 되어있었습니다.
그런데 화면인 View(Output)가 동시에 터치하는 영역인 Controller(Input)인 환경에서는
View와 Controller를 합친 더 밀접한 관계가 필요해서 이렇게 설계를 하지 않았을까 저는 추측을 하고 있습니다.
실제로 MVC 패턴을 적용했을 때 이런 설계 덕분에 아키텍처 패턴을 위한 코드가 적고 매끄럽게 돌아갔습니다.
UIKit환경에서 Controller(UIViewController)가 프레젠테이션 로직을 주로 관리합니다.
이게 저만 그랬을 수도 있는데 Model이 비즈니스 로직을 담당하는 건 들어보았어도
Controller가 프레젠테이션 로직을 관리한다는 소리는 잘 못 들어본 거 같습니다.
프레젠테이션 로직은 isHidden처럼 화면의 상태를 나타내는 로직입니다.
저는 예전에 Model이 비즈니스 로직을 담당한다고만 들었어서
가능한 모든 로직을 Model에 넘겼다가... 패턴을 준수하기 위한 코드가 엄청 복잡해졌던
그런 기억이 있습니다...
아무튼 비즈니스 로직과 프레젠테이션 로직을 명확하게 나누고 코드를 짜니까
훨씬 코드가 깔끔해졌습니다.
Input 및 데이터 전달
Controller(UIViewController)는 보통 Model과 View를 소유하고 있는 경우가 많습니다.
그래서 Model과 View에는 직접 참조로 업데이트나 동작을 시킬 수 있습니다.
// 모델을 소유하는 경우
let aModel = AModel()
// 뷰를 소유하는 경우
let customView:CustomView = {
let view = CustomView()
return view
}()
// 직접 참조로 업데이트나 동작을 시킬 수 있다.
userModel.updateData()
customView.label.text = "Update"
반대의 경우는 어떨까요?
view의 Input을 Controller(UIViewController)에 전달하거나
Model의 변경을 Controller(UIViewController)로 알리는 경우입니다.
View의 Input의 경우, UIButton일때가 많은데,
저는 Controller(UIViewController)에서 .addTarget()으로 연결하는게 제일 편했습니다.
let customView:CustomView = {
let view = CustomView()
// addTarget으로 Button의 Input을 처리한다.
view.aButton.addTarget(self, action: #selector(aButtonTapped), for: .touchUpInside)
return view
}()
// Button 동작
@objc func aButtonTapped() {
print("a 버튼이 눌렸어요")
}
UIButton이 아닌 경우나 Model의 변화를 Controller(UIViewController)에
알리는 경우는 클로져를 이용한 패턴도 좋습니다.
(didSet과 아래처럼 함수를 이용해서 일괄적으로 처리하면 더 편합니다.)
class AModel {
// didSet으로 데이터가 변경되면 자동으로 didChangeInfos함수 실행
var infos: [info]? {
didSet {
didChangeInfos?(self)
}
}
var didChangeInfos: ((AModel) -> Void)?
}
class AViewController: UIViewController {
let aModel = AModel()
override func viewDidLoad() {
super.viewDidLoad()
// 데이터 연결 처리
setBinding()
}
// aModel의 데이터가 변경되었을 때 실행할 동작들 연결
private func setBinding() {
aModel.didChangeInfos = { _ in
print("모델의 데이터가 바뀌었습니다.")
}
}
}
그 외에는
커스텀 델리게이트 패턴도 있습니다.
// 델리게이트 프로토콜 정의
protocol MyDelegate: AnyObject {
func didDoSomething()
}
// 델리게이트를 사용하는 객체
class AModel {
weak var delegate: MyDelegate?
// didSet으로 데이터가 변경되면 자동으로 didDoSomething함수 실행
var infos: [Info]? {
didSet {
delegate?.didDoSomething()
}
}
}
// 델리게이트를 구현하는 객체(extension으로 아래로 빼셔도 됩니다.)
class AViewController: UIViewController, MyDelegate {
let aModel = AModel()
override func viewDidLoad() {
super.viewDidLoad()
// DataManager 객체의 델리게이트 설정
aModel.delegate = self
}
// 델리게이트 프로토콜 메서드 구현
func didDoSomething() {
print("infos의 변경을 뷰에서 캐치했습니다.")
}
}
긴 글 읽어주셔서 감사합니다!
다음에는 MVVMProjectWithGitAPI로 돌아오겠습니다.
RxSwift도 학습해서 같이 적용할 생각이라 시간이 좀...걸릴겁니다.
아 그리고 이번 프로젝트는 MVCProjectWithGitAPI 였습니다!
아래는 프로젝트 링크입니다.
MVC
https://github.com/kangsworkspace/MVCProjectWithGitAPI
MVP
https://github.com/kangsworkspace/MVPProjectWithGitAPI
MVVM