본문 바로가기
Swift

[디자인 패턴] MVVM 요약 정리 with Swift

by yj.yoon 2023. 10. 23.

영어 잘하는 개발자 쨩쨩인게 좋은 해외 아티클이 많기 때문,,

요즘 단어부터 하고 있긴한디 그냥 잘했으면ㅎㅎㅎ

'쉽게 얻는건 쉽게 잃어' - 윤성빈 선수-

 

 

참고한 글(ref) : https://medium.com/swift-india/mvvm-2-a-deep-tour-3c8d9fa0cd53

 

MVVM-2: A Deep Tour

Here we are back again with the second part of our MVVM series. If you are new to MVVM, you would love to read my first blog of this…

medium.com

 


 

먼저 MVVM (Model, View, ModelView)에 대해 간단하게 요약하자면.

특징은 각각 독립적이며 모듈화가 가능하다.

command, data binding 패턴을 이용한다.

- 데이터 바인딩은 서로에게 데이터 변경을 알려줄 수 있는 방법이며, 대표적으로 observable가 있다.

 

 

<역할>

Model : 데이터

View : 사용자 UI, 입출력을 담당

View Model : View를 표현하기 위한 모델. 데이터를 가공하고 처리

 

 

<과정>

1. Model에서 데이터 구조화, 캡슐화

2. View에서 사용자의 액션(UI)을 받음 -> ViewModel 전달

3. ViewModel에서 Model에 데이터 요청

4. Model에서 ViewModel로 데이터 응답

5. ViewModel에서 데이터를 처리 -> View를 업데이트함 (data binding)

 

 

<프로젝트 구조 예>

(상위) Modules

(하위) Model

(하위) View

(하위) ViewModel

 

 


MVVM 단점(Disadvantages)

- MVVM for beginners is hard to put to use. (ㅠㅠㅠㅠㅠ)

- 여러 컴포넌트 사이에서의 통신과 데이터 바인딩이 어려울 수 있다.

- 중첩된 뷰 안에서 view model들과 상태를 관리하는 것과 복잡한 UI는 어렵다. (Managing view models and their state in nested views and complex UI’s is difficult.) <- 해석이 이게 맞나?ㅋㅋㅋㅠㅠㅠ

 

 

뷰의 코드 재사용성

"같은 뷰에서 여러 뷰 모델 사용하기"

 

 

Example - Model

"장소"에 대한 데이터 모델 만들기

enum PlaceType: String {
    case restaurant = "restaurant"
    case cafe = "cafe"
    case club = "club"
    
    static func allPlaceType() -> [PlaceType] {
        return [.restaurant, .cafe, .club]
    }
    
    func iconUrl() -> String {
        switch self {
        case .restaurant:
            return "url_1"
        case .cafe:
            return "url_2"
        case .club:
            return "url_3"
        }
    }
    
    func cellTitleText() -> String {
        switch self {
        case .restaurant:
            return "Top Restaurants nearby"
        case .club:
            return "Top Club nearby"
        case .cafe:
            return "Top Cafes nearby"
        }
    }
}

- enum으로 데이터 세트 정의

- 사용하기 쉽도록 함수(func) 생성

- static : 전역

 

struct Place {
    var name: String?
    var address: String?
    var type: PlaceType!
    var img: String?
    var rating: Double?
    var openStatus: Bool?
    
    // 속성 초기화
    init(attributes: [String: Any], type: PlaceType) {
        self.type = type
        self.address = attributes["vicinity"] as? String
        self.name = attributes["name"] as? String
        self.rating = attributes["rating"] as? Double
        
        if let openingHours = attributes["opening_hours"] as? [String: Any] {
            self.openStatus = openingHours["open_now"] as? Bool
        }
        
        setImage(attributes: attributes)
    }

    private mutating func setImage(attributes: [String: Any]) {
        guard let photos = attributes["photos"] as? [[String: Any]] else {return}
        guard photos.count > 0 else {return}
        guard let photoRef = photos[0]["photo_ref"] as? String else {return}
        self.img = "con_img_url"
    }
}

 - struct : 상태 및 동작을 캡슐화

- ? (옵셔널) : 속성의 값이 없을 수 있음 ex nill(=: null)

- ! (암시적 언랩핑 옵셔널) : 처음에는 nill이지만, 사용할 때는 값이 있는 경우. (안전한 상황에서 사용)

- as? : (지정 타입)으로 형변환하고, 형변환이 실패할 경우 nil을 할당

- mutating : 구조체나 열거형 내부에서 해당 인스턴스의 속성을 수정할 수 있도록 하는 키워드

- guard : guard문은 if와 다르게 조건이 false인 경우 실행 => guard (조건) else (조건이 false인 경우 실행되는 코드)

 

: setImage는 데이터의 유효성을 검사해 결과적으로 이미지의 url을 반환하는 함수이다.

photos라는 상수에 "photos"키에 해당하는 데이터를 할당 -> 이 값이 [String:Any] 가 아니면 함수가 종료(return)된다.

photoRef라는 상수에 photos의 첫 번째 데이터의 키가 "photo_ref"에 해당하는 값을 할당 -> 문자열이 아니면 종료된다.

여러 유효성 검사를 거친 후 self.img 속성을 설정 가능하다.

 

 


실제 기획 보면서 설계해보고 싶은데,, 아쉬운대로 기존 서비스 중인 프로젝트로 작성해봐야지