Skip to content

[승용] Demystify SwiftUI

Eric Kwon / 권승용 edited this page Jul 21, 2024 · 1 revision

SwiftUI는 우리 코드에서 무엇을 보고 있을까?

  • Identity
    • SwiftUI가 여러 번의 업데이트 동안 요소들이 동일한지 또는 서로 다른지 인식하는 방법
  • Lifetime
    • SwiftUI가 시간 경과에 따라 뷰와 데이터의 존재를 추적하는 방법
  • Dependencies
    • 인터페이스가 언제 왜 업데이트되어야 하는지 SwiftUI가 이해하는 방법
  • 요 세 가지 개념들이 언제 어떻게 무엇을 SwiftUI가 변경해야 하는지 결정하고, 온스크린에서 보이는 동적 유저 인터페이스를 생성한다.

Identity의 종류

  • Explicit identity
    • 커스텀 또는 데이터-드리븐 아이덴티파이어들
  • Structural identity
    • 뷰 계층의 위치와 뷰 타입으로 뷰들을 구분하는 아이덴파이어

Explicit Identity

  • 명시적으로 아이덴티티를 할당하는 방법
  • 강력하고 유연하지만, 어디선가 아이덴티티를 추적하는 존재가 필요
  • SwiftUI는 아래와 같이 명시적 아이덴티티를 사용한다.
List {
	Section {
		ForEach(resqueDogs, id: \.dogTagID) { rescueDog in
			ProfileView(rescueDog)
		}
	}
	Section("Status") {
		ForEach(adoptedDogs, id: \.dogTagID) { rescueDog in
			ProfileView(rescueDog, foundForeverHome: true)
		}
	}
}

ScrollViewReader { proxy in
	ScrollView {
		HeaderView(rescueDog)
			.id(headerID)

		Text(rescueDog.backstory)

		Button("Jump to Top") {
			withAnimation {
				proxy.scrollTo(headerID)
			}
		}
	}
}

Structural Identity

  • 명시적으로 식별자를 부여하지 않아도 된다고 해서 identity가 없는 뷰가 존재하는 것은 아니다.
  • 명시적이지는 않아도 모든 뷰가 식별자를 가지고 있다.
  • SwiftUI는 뷰 계층구조를 사용해 뷰들에 대한 암시적 식별자(implicit identity)를 생성한다.
  • SwiftUI는 API 전반에서 구조적 아이덴티티를 사용하며, 그 대표적인 예시로 View 코드 내에서 if문과 같은 조건문을 사용하는 경우가 있다.
// Structural Identity in SwiftUI

var body: some View {
	if rescueDogs.isEmpty {
		AdoptionDirectory(selection: $rescueDogs)
	} else {
		DogList(rescueDogs)
	}
}
  • 조건문의 구조는 각 뷰를 식별하는 명확한 방법을 제공한다.
  • 첫 번째 뷰는 조건문이 참일 때만 보여지고, 두 번째 뷰는 조건문이 거짓일 때만 보여진다.
    • 이는 두 뷰가 비슷해 보여도, 항상 두 뷰를 구분 가능하다는 의미이다.
  • 다만 이는 SwiftUI가 이러한 뷰들이 항상 제자리에 위치하고 자리를 바꾸지 않음을 보장할 수 있을 때만 제대로 작동한다.
    • SwiftUI는 뷰 계층구조의 타입 구조를 통해 자리를 바꾸지는 않는지 살펴본다.

Lifetime & Identity

  • identity는 시간이 지나며 변하는 다양한 값에 대한 안정적인 요소를 정의할 수 있게 해 준다.
    • 값은 변해도 뷰는 element는 변하지 않음!
    • 즉 시간이 지남에 따른 연속성을 도입 가능 뷰가 처음 생성되고 나타날 때, SwiftUI는 이전에 설명한 기술들을 사용해 뷰에 identity를 부여한다.
  • 시간이 지남에 따라 뷰를 위한 새로운 값들이 생성된다.
  • 그러나 SwiftUI의 입장에서는 이 값들을 같은 뷰를 나타낸다.
  • 뷰의 identity가 변하거나 뷰가 제거될 때 뷰의 lifetime도 종료된다.
  • 따라서 뷰의 lifetime을 이야기할 때에는 해당 뷰의 identity가 살아있는 기간을 이야기하는 것이다.
  • 뷰의 identity와 lifetime을 연결짓는 것은 SwiftUI가 어떻게 우리의 state를 유지하는가를 이해하기 위해 필수적이다.

State는 Identity의 Lifetime과 묶여 있다.

var body: some View {
	if dayTime {
		CatRecorder()
	} else {
		CatRecorder()
			.nightTimeStyle()
	}
}
  • 위 예제에서는 두 갈래로 분리된 같은 뷰가 있다.

  • 이전에 배운 내용을 떠올려보면, 구조적 identity로 인해 두 뷰는 서로 다른 identity를 가지게 된다.

    • 이러한 identity의 다름은 애니메이션에도 영향을 미치지만, State의 지속성에도 영향을 미친다.
  • body를 계산하고 true 분기점으로 진입하면 SwiftUI는 State의 초기값을 사용해 영구 저장소를 할당한다.

  • 뷰의 lifetime동안 State 값은 다양한 action들에 의해 변경되고, 저장소는 지속적으로 유지된다.

  • 그러나 dayTime이 false로 변경되고 false 분기점으로 진입하면 무슨 일이 일어날까?

    • State의 초기값을 사용해 else 아래의 뷰를 위한 새로운 저장소를 할당하게 된다.
    • 왜냐하면 두 뷰는 서로 다른 identity, 즉 서로 다른 lifetime을 가지는 개별적인 뷰이기 때문.
  • 그리고 첫 번째 true 뷰를 위한 저장소는 곧바로 할당 해제 된다. 그런데 만약 다시 dayTime이 true로 변경된다면 어떻게 될까?

  • 다시 true 뷰를 위한 새로운 초기값을 가진 저장소가 생기고, false 뷰를 위한 저장소가 할당 해제 된다.

  • 요점은 identity가 변경되면 State도 대체된다는 점을 유의해야 한다는 것이다.

  • 굉장히 중요한 포인트 : State의 지속성은 View의 lilfetime과 연결되어 있다.

    • State lifetime = View lifetime
  • 이는 뷰의 본질인 State를 명확하게 분리해 identity와 연결지을수 있는 강력한 컨셉이다.

    • 나머지 모든 것들은 이 개념으로부터 파생될 수 있다.

Data의 identity로 명시적 identity 제공하기

  • 데이터는 너무나도 중요해서, SwiftUI는 데이터의 identity를 명시적 identity의 형태로 사용하는 data-driven 구조체들을 가지고 있다.
    • ForEach
    • confirmationDialog() / alert()
    • List / Table / OutlineGroup
  • 그 전형적인 예시는 ForEach!
  • ForEach에 대해 잘 알아보기 위해 ForEach를 초기화할 수 있는 모든 방법들을 알아보자.
  • 가장 간단한 형태는 constant range를 가지는 것
ForEach(0..<5) { offset in
	Text("\(offset)")
}
  • SwiftUI는 range 내의 offset을 사용해 뷰 빌더가 생산한 각각의 뷰들을 식별한다.
  • 이는 뷰의 수명동안 identity가 안정적으로 제공되는 것을 보장한다.
  • 정수 리터럴이 아닌 변수나 상수를 사용해 constant range가 아닌 dynamic range를 초기자에 집어넣으면 warning 발생

Dependency Graph

  • 뷰 계층구조는 트리 형태이지만, 의존 관계는 그래프 형태이다.
  • 이러한 그래프 구조는 SwiftUI가 새로운 body를 필요로 하는 뷰들만을 효율적으로 업데이트하는 것을 가능하도록 해주기 때문에 중요하다.
  • 의존성 그래프의 중추는 identity이다.
  • 모든 뷰는 명시적으로든 구조적으로든 identity를 가지고, SwiftUI는 이 identity를 통해 변경사항을 올바른 뷰에 라우팅하고 UI를 효율적으로 업데이트한다.

Identifier 안정성

  • Identifier의 수명이 뷰의 수명과 직결되기 때문에, Identity의 안정성은 굉장히 중요하다.
    • 안정적이지 못한 Identifier는 짧은 뷰 수명을 야기할 수도 있다.
  • 안정적인 Identifier를 가지면 SwiftUI가 계속해서 뷰를 위한 저장소를 생성할 필요가 없고, 의존성 그래프를 업데이트하는 작업을 줄일 수 있기 때문에 성능 향상에도 도움이 된다.
  • 또한 안정적인 Identifier는 State를 잃는 상황을 방지하도록 해 준다.
  • 안정적인 Identifier는 랜덤하지 않고 안정적인, 고유한 Identifier이다.
Clone this wiki locally