THIS IS ELLIE

스탠포드 SwiftUI강의 복습하기 Lecture2 본문

개발/SwiftUI

스탠포드 SwiftUI강의 복습하기 Lecture2

Ellie Kim 2021. 1. 27. 03:48

MVVM은 디자인 패러다임이다.
MVVM 없이는 SwiftUI를 사용할 수 없다.

모델
앱의 백앤드 역할 그리고 UI와 View에 독립적이다.
그렇기 때문에 아래와 같이 SwiftUI패키지를 추가하지 않는다.

import SwiftUI

데이터와 로직을 가진다.
예를 들어 카드 매칭 게임이 있으면 카드는 데이터가 되고 매칭 하는 부분은 로직이 된다.


모델을 반영한다.
상태를 가지지 않고 가질 필요가 없다.
리액티브 하기 때문에 모델이 변경될 때 뷰가 자동적으로 변경된다.

뷰모델
뷰를 모델에 바인드 한다. (바인딩)
번역가와 같다.


// 모델에서 뷰로
만약에 모델 구조체에 변경이 있다면 뷰모델한테 변경이 있음을 알려준다.
뷰는 뷰모델을 관찰하고 있다가 자동적으로 뷰를 그린다.
(즉 뷰모델은 뷰에 대한 포인터를 가지지 않으며, 뷰에게 변경 사항을 직접적으로 전달하지 않는다.) 

// 뷰에서 모델로
뷰에서 버튼이 눌리거나 제스처에 액션이 발생하면 인텐트 함수를 호출한다.
그리고 뷰모델은 모델을 수정한다.

// 위 두 상황을 합치면 한 루프
모델이 수정되었으니 변경사항이 있다고 뷰모델에게 노티가 가고 뷰모델을 구독하고 있던 뷰는 자동적으로 뷰를 그릴 것이다.


구조체와 클래스 비교


모델 생성

모델 MemoryGame생성을 생성해보자.

모델은 함수와 변수로 이 모델이 무엇을 하는지 알 수 있어야 한다.
UI적인게 없어서 import Foundation으로 둬도 상관없다. 
여기서 CardContent는 무슨 타입이어야 하는지 상관없다.

뷰모델 생성

뷰모델 EmojiMemoryGame을 생성해보자.

뷰모델은 UI 것이 포함되어 있는 SwiftUI 파일로 만든다.
뷰모델은 뷰와 모델의 포털 역할을 한다.
뷰모델을 클래스로 한 이유는 힙 포인터이기 때문에 접근하기 쉽기 때문이다.

모든 뷰는 뷰모델의 포인터를 가진다.
하지만 같은 뷰모델을 사용하면 문제가 될 수 있다.

현재 모델, 뷰모델, 뷰의 상황을 집이라고 생각해보자.
뷰는 그 집안에 사는 것이라고 생각하고 뷰모델은 앞문이라 생각하고 모델은 밖이라고 생각하자.
앞문이 열려있다고 생각해보자. 그럼 집 안에 있는 사람들이 앞문을 통해 밖으로 나가게 된다.

뷰모델은 뷰에서 포인터를 가지고 있기 때문에 모든 것을 공유한다.
즉 클래스로 뷰모델을 만들어 준 것이 문제가 생길 수 있다. (더 복잡한 코드면 더 문제)
위험을 줄이기 위해 private으로 프로퍼티 접근제어자를 설정하여 제약을 줄 수 있다.
위 예로 따지면 private으로 제약을 주면 앞문을 닫는 느낌이다.

private(set)으로 선언할 수도 있다.
문인데 유리문이라고 생각하면 된다.
(바깥을 볼 수는 있는 상황이지만 함부로 나가지는 못하는 상황)

유리문이라 밖에 마음대로 나갈 수는 없지만 밖을 볼 수 있게 인터콤을 제공해주자.
(제한된 방법으로 밖을 볼 수 있는 걸 제공)

var cards: Array<MemoryGame<String>.Card> {
    return model.cards
}

 

위 같이 함으로써 모델의 카드를 접근할 수 있게 한다.

아래 함수가 정적 함수인 이유는 초기화가 되기 전까지 그 함수를 이용할 수 없기 때문에 정적 함수로 선언했다.

static func createMemoryGame() -> MemoryGame<String> {
    let emojis: Array<String> = ["🏆","🧘‍♂️"]
    
    return MemoryGame<String>(numberOfPairsOfCards: emojis.count) { pairIndex in
        return emojis[pairIndex]
    }
}

사용할 때는 함수로 타입.정적함수 호출로 사용한다.

private var model: MemoryGame<String> = EmojiMemoryGame.createMemoryGame()

 

ForEach부분은 이터러블 한 것이 와야 한다.
그리고 그 이터러블한 것은 각각을 구분할 수 있어야 한다.

struct Card: Identifiable {
    var isFaceUp: Bool = false
    var isMatched: Bool = false
    var content: CardContent
    var id: Int
}

각각을 구분하기 위해 카드 모델 구조체에 Identifable 프로토콜을 선언해준다.
카드에 애니메이션을 적용하거나 카드 순서를 움직여본다고 가정하자.
어떤 카드를 움직여야 하는지 또 각 카드의 싱크를 맞추기 위해선 어떤 카드를 움직여야 하는지 구분할 수 있어야 한다.
카드 구조체 자체는 구분할 수 있는 것이 없기 때문에 카드 모델 구조체에 Identifable 프로토콜을 선언해준다.

뷰는 뷰모델의 인텐트 함수를 사용해 모델과의 커뮤니케이션을 할 수 있게 된다.

CardView(card: card).onTapGesture {
    viewmodel.choose(card: card)
}

 

resource:
cs193p.sites.stanford.edu/
www.youtube.com/watch?v=4GjXq2Sr55Q

반응형