[iOS] MVVM

2 답변 글타래를 보이고 있습니다
  • 글쓴이
    • 5anniversary
      참가자
      • 글작성 : 14
      • 답글작성 : 7

      안녕하세요 🙂

      오늘은 이번학기 새로 시작할 프로젝트를 MVVM 디자인 패턴으로 프로젝트를 만들어가기 위해,
      MVVM 디자인 패턴에 대해 살짝쿵 공부를 해보았습니다.

      이번 프로젝트에서 MVVM 디자인 패턴으로 구조를 구성하기로 생각을 한 이유는 현재 진행중인 동아리 프로젝트에서는 MVC로 하는데 점점 가면 갈수록, 프로젝트가 비대해질수록, 프로젝트의 구조를 파악하고 고쳐야할 부분을 찾기가 힘들다는 점이 가장 컸습니다.

      MVVM 디자인 패턴의 기본적인 구조를 그려놓은 이미지입니다
      아무튼!!

      Model은 어플리케이션 데이터를 가지고 있고, Model들은 보통 struct나 간단한 class로 구성됩니다,

      View는 스크린에 보여지고, 컨트롤하는 구조입니다. 보통은 UIView로 만들어집니다

      ViewModel은 model 정보를 view에 표시할수있는 값으로 변환합니다. 보통 Class로 만들어지기 때문에 reference로 사용될 수 있습니다.

      이런 기본적인 구조를 파악했으니 코드가 어떻게 구성되는지 보면서 디자인 패턴에 대해 파악을 해볼까요?

      아래의 예제 코드들은 참고에 있는 페이지에서 가져왔습니다.

      import PlaygroundSupport
      import UIKit
      
      // MARK: - Model
      public class Pet {
        public enum Rarity {
          case common
          case uncommon
          case rare
          case veryRare
        }
      
      public let name: String
        public let birthday: Date
        public let rarity: Rarity
        public let image: UIImage
      
      public init(name: String,
                    birthday: Date,
                    rarity: Rarity,
                    image: UIImage) {
          self.name = name
          self.birthday = birthday
          self.rarity = rarity
          self.image = image
        }
      }
      

      Pet이라는 이름을 가진 Model에서 모든 pet에게 있을 name, birthday, rarity, image를 정의해줍니다.
      name과 image의 경우에는 View에 바로 표시할 수 있지만, birthday와 rarity의 경우에는 View에서 바로 표시를 할 수 없기때문에 ViewModel에서 변형이 필요합니다.

      // MARK: - ViewModel
      public class PetViewModel {
      
      // 1
        private let pet: Pet
        private let calendar: Calendar
      
      public init(pet: Pet) {
          self.pet = pet
          self.calendar = Calendar(identifier: .gregorian)
        }
      
      // 2
        public var name: String {
          return pet.name
        }
      
      public var image: UIImage {
          return pet.image
        }
      
      // 3
        public var ageText: String {
          let today = calendar.startOfDay(for: Date())
          let birthday = calendar.startOfDay(for: pet.birthday)
          let components = calendar.dateComponents([.year],
                                                   from: birthday,
                                                   to: today)
          let age = components.year!
          return "(age) years old"
        }
      
      // 4
        public var adoptionFeeText: String {
          switch pet.rarity {
          case .common:
            return "$50.00"
          case .uncommon:
            return "$75.00"
          case .rare:
            return "$150.00"
          case .veryRare:
            return "$500.00"
          }
        }
      }
      

      ViewModel에서 위 코드에서 한 일들을 정리해보면

      1. pet과 calenadar라는 두 개의 private property를 만들고 init에서 초기화를 시켜줍니다.
      2. name과 image에 대해 pet 프로퍼티에서 받아옵니다.
      3. pet의 birthday에서 받아와 계산을 하는 ageText를 선언합니다. retrun은 O years old로 계산됩니다.
      4. 마지막으로 pet의 rarity에서 enum을 받아와 return되는 값을을 switch문으로 정의합니다.

      이 다음으로는 UIView에서 ViewModel의 계산 방식을 가지고 사용자에게 보여줍니다.

      // MARK: - View
      public class PetView: UIView {
        public let imageView: UIImageView
        public let nameLabel: UILabel
        public let ageLabel: UILabel
        public let adoptionFeeLabel: UILabel
      
      public override init(frame: CGRect) {
      
      var childFrame = CGRect(x: 0, y: 16,
                              width: frame.width,
                              height: frame.height / 2)
      imageView = UIImageView(frame: childFrame)
      imageView.contentMode = .scaleAspectFit
      
      childFrame.origin.y += childFrame.height + 16
      childFrame.size.height = 30
      nameLabel = UILabel(frame: childFrame)
      nameLabel.textAlignment = .center
      
      childFrame.origin.y += childFrame.height
      ageLabel = UILabel(frame: childFrame)
      ageLabel.textAlignment = .center
      
      childFrame.origin.y += childFrame.height
      adoptionFeeLabel = UILabel(frame: childFrame)
      adoptionFeeLabel.textAlignment = .center
      
      super.init(frame: frame)
      
      backgroundColor = .white
      addSubview(imageView)
      addSubview(nameLabel)
      addSubview(ageLabel)
      addSubview(adoptionFeeLabel)
      
      }
      
      @available(*, unavailable)
        public required init?(coder: NSCoder) {
          fatalError("init?(coder:) is not supported")
        }
      }
      

      위 PetView에서는 사용자에게 보여줄 4가지의 subView를 만듭니다.
      먼저 petView의 크기와 위치를 정의해주고
      각각 4가지의 subView의 위치와 크기를 지정해줍니다.

      // MARK: - Example
      // 1
      let birthday = Date(timeIntervalSinceNow: (-2 * 86400 * 366))
      let image = UIImage(named: "stuart")!
      let stuart = Pet(name: "Stuart",
                       birthday: birthday,
                       rarity: .veryRare,
                       image: image)
      
      // 2
      let viewModel = PetViewModel(pet: stuart)
      
      // 3
      let frame = CGRect(x: 0, y: 0, width: 300, height: 420)
      let view = PetView(frame: frame)
      
      // 4 
      view.nameLabel.text = viewModel.name
      view.imageView.image = viewModel.image
      view.ageLabel.text = viewModel.ageText
      view.adoptionFeeLabel.text = viewModel.adoptionFeeText
      
      // 5
      PlaygroundPage.current.liveView = view
      

      마지막으로 사용자에게 보여지는 class부분입니다
      해당하는 코드는 playground로 작성이 되어있기 때문에 보시는 여러분들이 Single View App에서 사용하실때에는 UIViewController에서 사용을 해주시면 되겠습니다.
      사용한 방법은
      1. stuart의 이름을 가진 Pet을 생성해주고
      2. 생성한 stuart를 사용해 ViewModel을 생성해줍니다.
      3. 미리 만들어둔 petView를 이용해 view를 생성해줍니다.
      4. 위 코드에서 만들어둔 petViewModel을 이용해 view안에 넣어줍니다.
      5. 마지막으로 사용자에게 보여주는 코드!!입니다.

      위와 같은 구조로 사용되는 MVVM인데요 하나씩 정리를 하면서 보니깐
      이해가 어렵지는 않네요.

      Model을 만들어 구조를 잡아준 다음!
      ViewModel에서 출력될 값들을 정의해주고,
      View를 만들어 사용자에게 보여질 UI를 구성한 뒤
      UIViewController에서 구조에 맞는 값들을 ViewModel에 넣어 View를 통해 보여주는 구조로 이해했습니다.

      • 이 게시글은 5anniversary에 의해 4 years, 8 months 전에 수정됐습니다.
      • 이 게시글은 5anniversary에 의해 4 years, 8 months 전에 수정됐습니다.
      • 이 게시글은 5anniversary에 의해 4 years, 8 months 전에 수정됐습니다.
    • 야곰
      키 마스터
      • 글작성 : 37
      • 답글작성 : 579

      멋진 정리네요!
      질문이 있습니다~

      • Model은 구조체로 주로 구현한다고 했는데 View Model은 주로 클래스로 구현하는 이유는 무엇일까요?
      • 대부분의 타입과 프로퍼티를 public 키워드를 사용해서 정의했는데, 써주지 않은것과 차이는 무엇인가요? 왜 public으로 정의했을까요?
    • 5anniversary
      참가자
      • 글작성 : 14
      • 답글작성 : 7
      • ViewModel은 직접적으로 상속을 받고 있고 class의 경우에는 call by reference로 알고있습니다. 따라서 Model보다 좀 더 자세한 내용이 쓰여있는 ViewModel의 경우에는 사이즈가 크기때문에 class를 사용한다고 이해했습니다!
      • 두번째 질문에 대해서는 좀 더 공부를 하고 답변하겠습니다! 단순히 번역을 통해 작성한 코드이고 의례적으로 사용한 public이라 좀 더 자세한 공부를 해야할 것 같습니다!!
2 답변 글타래를 보이고 있습니다
  • 답변은 로그인 후 가능합니다.

logo landscape small

사업자번호 : 743-81-02195
통신판매업 신고번호 : 제 2022-충북청주-1278 호
고객센터 : 카카오톡채널 @yagom