TIL(Today I Learned)

11월 21일(일)

학습내용

  • SwiftUI(CS193p Lecture 4)
    • 아래의 EmojiMemoryGame 타입은 class로 정의 되어있다. class 이므로 game은 포인터이며, let이므로 이 포인터가 지닌 주소값이 변경되지 않는다. 따라서 객체 내부의 프로퍼티 값은 변할 수 있다. 만약 struct이라면 game 변수의 값 자체가 변경되지 않음을 의미할 것이다. 이 경우 game내의 프로퍼티가 변할 수 없다.

        let game = EmojiMemoryGame() 
      
    • Identifiable 프로토콜을 채택하면 id라는 프로퍼티를 정의해야한다.
    • private + computed property vs private(set)

        // private + computed property
        private var model: MemoryGame<String> = createMemoryGame()
              
        var cards: Array<MemoryGame<String>.Card> {
            model.cards
        }
              
        // private(set)
        private(set) var model: MemoryGame<String> = createMemoryGame()
      
      • private + computed property은 cards를 접근하기 위해서 model을 드러내지 않는다는 장점이 있다. private(set)은 computed property을 정의하지 않아도 된다는 장점이 있다.
    • CardView의 프로퍼티
      • CardView는 카드 내용 한개만 표시하므로 cards가 아닌 card 하나만 프로퍼티로 전달해주어야 한다. 뷰가 필요한 최소한의 것만 전달해주는 것이 좋다. (cards 전달 X, ViewModel 전달 X)
      • 또한 View는 read-only이므로 전달받은 데이터를 let으로 해주는게 좋다.(var X)

          struct CardView: View {
              let card: MemoryGame<String>.Card
              ...
          }
        
    • User Intent는 ViewModel에 구현한다.
      • User Intent는 사용자의 입력
    • ViewModel은 ObservableObject를 채택해야 View에게 값의 변화를 알려줄 수 있다.
      • 수동으로 값을 알려주기 위해서는 var objectWillChange: ObservableObjectPublisher 을 정의하고 objectWillChange.send()을 호출함으로써 변화를 publish할 수 있다.
      • 하지만 @Published를 이용하면 이를 자동으로 가능하게 해준다.
      • 또한 ViewModel을 참조하는 View에서는 @ObservedObject라고 프로퍼티 앞에 붙여주어야 한다.

          class EmojiMemoryGame: ObservableObject {
              @Published private var model: MemoryGame<String> = createMemoryGame()
              ...
          }
                    
          struct ContentView: View {
              @ObservedObject var viewModel: EmojiMemoryGame
          }
        
      • model이 변화하면 ViewModel이 publish하고, View는 그때마다 재생성된다.
    • Enum
      • It can only have discrete states
      • Value Type (값 복사해서 전달)
      • case마다 associated data를 가질 수 있다.
        • case .hamburger(let pattyCount)
      • switch로 각각의 case을 구분해서 분기할 수 있고, default로 나머지 case를 분기 받을 수 있다.
      • 연산 프로퍼티를 정의할 수 있지만, 저장프로퍼티는 불가능, 또한 메서드도 정의할 수 있다.
      • CaseIterable을 채택하면 allCases 라는 static 변수가 생성되는데, 이 변수를 이용해 모든 케이스를 순회할 수 있다.
      • GridItem도 아래와 같이 enum을 이용한다.

          struct GridItem {
              enum Size {
                  case adaptive(minimum: CGFloat, maximum: CGFloat = .infinity)
                  case fixed(CGFloat)
                  case flexible(minimum: CGFloat = 10, maximum: CGFloat = .infinity)
              }
          }
        
    • Optionals
      • Optional도 enum이다. 두가지 상태 is set(some), 또는 not set(none)이 가능하다.

          enum Optional<T> {
              case none
              case some(T) // T 타입의 associated Value를 갖는 것이다.
          }
        
      • Optional은 값의 “not set”, “unspecified “, “undetermined “을 위해서 사용한다.

          var hello: String?
          var hello: Optional<String> = .none
                    
          var hello: String? = "hello"
          var hello: Optional<String> = .some("hello")
                    
          var hello: String? = nil
          var hello: Optional<String> = .none
        
      • if let을 이용하여 안전하게 언래핑할 수 있다. (!, exclamination point은 크래쉬가 날 위험이 있다.)
      • ??을 이용하여 nil일 경우의 기본 값을 정할수 있다. Optional defaulting이라고 하며 ??을 nil-coalescing operator라고 한다.
      • Optional Chaining
        • let y = x?.foo()?.bar?.z 중간에 nil이 있으면 y도 nil이 할당
    • Tip
      • 타입에서 중요하지 않은 프로퍼티는 맨 아래쪽에 둔다.
      • Preview 하단의 pin을 활성화하면 탭을 이동해도 preview가 사라지지 않고 창에 고정된다.
      • cards(카드 배열)의 indices 프로퍼티를 이용해서 0…<cards.count를 대체하여 순회할 수 있다.

          ~~for index in 0...<cards.count~~
          for index in cards.indices
        
      • ViewModel이 모델(프로퍼티) 변화를 감지 할 수 있는 이유는 Swift가 struct의 변화를 감지할 수 있기 때문이다. 하지만 프로퍼티가 class일 경우는 불가능하다. (영상 1:27:30 부근)

문제점/고민한점

  • 왜 SwiftUI는 ForEach의 파라미터는 Idetifiable을 해야하는가?

해결방법

  • View를 id로 구분하기 위하여.
    • 왜 View를 id로 구분해야하지?
      • 특정 값에 해당하는 View를 찾아내기 위해
      • 만약 같은 값을 View로 나타낼 경우 어떤 View를 변화시킬건지 구분할 수 가 없게됨
        • 값의 index를 뷰의 index와 비교해서 구분할 수도 있잖아?
          • 만약에 collection에 값의 추가와 삭제가 동시에 여러번 일어났는데 이때는 그럼 index를 어떻게 판단할 건데?
          • 그렇다고 index가 바뀐 것만 뷰에 알려줄수 있는 것도 아니 잖아? 뷰는 read only이니깐.
          • 그래서 id를 통해 해당 값의 id에 해당하는 뷰를 수정하는거지.

참고