SwiftUI는 뷰를 어떻게 구분하고 업데이트할까요? 또 Identity, Lifetime등 용어는 무엇일까요?
오늘은 이를 정리해보겠습니다.
우선 이를 알기위해 알아야 할 사전 지식이 있습니다.
동등성(equality)와 동일성(Identity)입니다.
동등성(Equality)
이 둘은 같을까요?
Equatable로 비교하면 이 둘은 같습니다.
이렇게 상태만을 비교하면 이 둘은 같다고 할 수 있습니다.
동일성
한 clamp가 있다고 가정해봅시다.
우리는 이름도 같고, 나이도 같기 때문에 같은 clamp라고 생각 할 수 있습니다.
하지만 Equatable로 비교하면 다르다고 나옵니다. 상태가 다르니까요
만약 우리가 저 둘을 동일한 clamp라고 생각하려면, 즉 같은 clamp로 "식별"하려면 무언가가 필요합니다
예를 들어 주민등록번호가 될 수 있겠지만 코드에서 사용할 순 없기 때문에 무언가 식별자가 필요합니다.
이를 위해 Identifiable 프로토콜이 탄생하게 되었습니다.
이렇게 되면 good, bad 모두 같은 사람이라고 판단할 수 있습니다.
이런 경우 이 둘의 identity(동일성)이 같다고 할 수 있네요.
이런 사전지식이 왜 필요할까요
UIKit은 클래스 타입이기 때문에 뷰를 할당하므로 얻는 포인터가 그 뷰의 명시적 ID가 될 수 있습니다.
SwiftUI는 value타입이기 떄문에 이런 값을 지속적으로 사용할 수 없습니다.
그래서 Identity가 등장하게 되었으며 데이터 및 업데이트 주기에 영향을 미칩니다.
SwiftUI는 값타입입니다. 값타입은 복사에 의해 값이 전달되므로 원본을 변경하지 않고 각각의 복사본이 독립적으로 존재하게되죠. 변수에 값을 할당하거나 함수에 인자로 전달할 때 원본 값이 아닌 독립된 복사본이 생성됩니다. 이는 각 복사본이 메모리 상에 다른 위치에 존재한다는 뜻이며 이러한 특성때문에 해당 뷰를 지속적으로 추적하기 힘듭니다.
만약 SwiftUI가 내부적으로 뷰의 메모리 주소를 ID로 사용한다면 뷰가 재생성 될 때 마다 새로운 id가 할당됩니다.
SwiftUI는 뷰의 상태와 속성의 변화를 감지하고 변화된 부분만을 효율적으로 업데이트합니다.
뷰의 새로운 상태와 이 전 상태를 비교하게 되는데 이 때 id가 다르다면 상태 비교가 불가능하게 됩니다.
View Identity
- 명시적(Explicit) Identity
- 구조적(Structural) Identity
그렇다면 뷰는 어떻게 id를 갖게 될까요
명시적(Explicit) Identity
다음과 같이 직접 id를 설정해줍니다.
하지만 사소한 뷰 하나하나 id를 설정하는건 매우 비효율적이죠.
구조적(Structural)Identity
SwiftUI는 뷰 계층구조를 사용해 암시적 Identity를 생성합니다.
즉 전반에 걸쳐 구조적 ID를 생성하므로 모든 뷰의 id를 생성할 필요는 없습니다.
만약 뷰에서 if조건문을 사용하는 경우 상황에 따라 다른 뷰를 보이도록 사용하는데, SwiftUI는 정적인 if문 같은 경우 뷰 계층 구조에서 서로 다른 브랜치를 만들며 각각 고유한 Id를 가지게 됩니다.
some View =
_ConditionalContent<
EmptyView, // true
UserListView // false
>
이렇게 변환됩니다. View는 true, false에 대한 제네릭 타입을 받게됩니다. 그러므로 각 상황에 따라 다른 뷰라는걸 보장하며 이 보장을 통해 내부적으로 각각 다른 id를 부여합니다.
LifeTime 뷰와 데이터의 수명
View Value Lifetime
상위 뷰에서 SomeView를 호출한 상태입니다.
그럼 description = "행복하다" 라는 정보를 갖는 메모리를 생성합니다.
만약 어떤 인터랙션을 통해 description = "배고프다" 로 변경되면 어떤 일이 일어날까요?
새로운 공간에 description = "배고프다"에 대한 값을 세팅합니다. 그리고 이전 값과 현재값을 변경한 후 변경되었다면 이전 값을 해제하게 됩니다.
View Lifetime
SomeView의 id는 view가 init되고 onAppear가 불렸을 시 SwiftUi는 SomeView에 id를 할당합니다.
시간이 흘러 새로운 value들이 업데이트 되어도 id는 동일합니다. 외부에 의해 변경되거나 해당 뷰가 제거되었을 때 id의 수명은 종료됩니다.
State Lifetime
SwfitUI의 @State, @StateObject를 뷰에서 확인하면 해당 데이터는 뷰의 lifetime동안 유지해야 한다는 것을 압니다.
뷰의 수명은 뷰의 id수명과 연관있습니다.
State로 생성된 저장공간은 뷰의 id 수명과 지속되는 저장소입니다.
즉 onAppear가 불렸을 때 State 변수를 메모리에 할당하고 id가 변경되지 않는다면 데이터가 변경되어도 동일한 공간을 사용합니다. view의 body가 다시 호출되어도 동일한 공간을 사용합니다.
그렇다면
이 EmptyView와 UserListView는 다른 브랜치로 정의되고 id도 다를것이고, 조건이 true인 뷰에 State를 영구적인 저장 공간에 할당할 것입니다. false인 곳은 기존과 다른 id이기 때문에 사용하던 공간이 해제되겠습니다.
그렇다면 적절한 id를 사용하는 방법은 무엇일까요?
명시적 identity를 사용하는 경우 안정적이고 고유한 id를 사용해야합니다.
예를 들어 name, number와 같은 프로퍼티는 중복될 수 있습니다. id와 같은 고유한 요소를 사용해야합니다.
또한 변하지 않아야 합니다. id가 계속 변하지 않고 안정적이면 성능에 도움을 줍니다. 영구적으로 변하지 않을 id를 선택해야합니다.
구조적 identity를 사용하는 Dependent Code를 권장합니다.
if 조건문은 뷰 계층 구조에서 다른 브랜치를 사용하기 때문에 다른 id를 갖게됩니다.
그렇기 때문에 두 가지 상태를 표현하고 싶다면 가능한 브랜치를 나누기보단 modifier를 이용해서 상태값을 변경하는 것을 권장합니다.