본문 바로가기

iOS/SwiftUI

SwiftUI 내부 동작 원리

SwiftUI 내부 작동 원리

When SwiftUI looks at your code, what does it see?

이번시간에는 swiftUI가 우리의 코드를 볼 때 내부 동작에 대해서 한번 학습해 보겠습니다.

이를 위해서 아래의 세가지를 살펴볼 예정입니다!

Identity, Lifetime, Dependencies

Identity는 SwiftUI가 앱의 다양한 업데이트 시 각 element를 동일하거나 혹은 별개로 인식하는 방법입니다.

Lifetime은 시간의 흐름에 따른 뷰와 데이터의 존재를 추적하는 방법입니다.

Dependency는 인터페이스를 업데이트해야하는 시기와 이유를 이해하는 방법입니다.

이 개념들을 통해 SwiftUI는 변경해야 하는 사항, 방법, 시기를 결정하여 화면에 표시되는 동적 UI를 만들어 냄!

대부분의 내용은 네카라쿠배 5개 앱으로 완성하는 iOS 앱 개발 초격차 패키지 Online. 강의와

Demystify SwiftUI - WWDC21 - Videos - Apple Developer

위 영상을 참조했습니다!!!

이제 아래에서 자세히 다뤄보겠습니다!!!!!!

Identity

Identity에 대해 학습하기 전 동일성(Identity)과 동등성(Equality)에 대해 간단히 비교해 보겠습니다.

객체지향의 관점에서 A, B 객체가 있고 두 객체의 주소값은 다르다고 가정해 보겠습니다.

그러면 둘은 서로 다른 주소값을 가지므로 완전히 같다고 할 수 없습니다

이러한 관점에서 보자면

  • 동일성(Identity) - 완전히 같다(위 예에서는 주소값이 같아야 합니다)
  • 동등성(Equality) - 같은 정보를 갖는다(값은 같지만 주소값은 다를 수 있습니다)

이라고 생각할 수 있을 것 같습니다

동일성과 동등성

 

동일성과 동등성

안녕하세요. 그린입니다🟢 이번 포스팅에서는 동일성과 동등성에 대해 알아보겠습니다💁🏻 동일성과 동등성 그냥 알아보기전 뜻풀이부터 나름대로 추측해볼까요? 동일성은 A와 B가 전부 같

green1229.tistory.com

이제 진짜 Identity에 대해 알아보겠습니다.

SwiftUI는 identity를 통해서 element를 같은지 다른지를 구분합니다.

예를 들어서 확인해 보겠습니다.

 

위 두 개의 이미지는 서로 다른 뷰인가요?? 아이콘의 위치와 색깔만 다른 같은 뷰일까요?

  • 다른 뷰라면 fadeing in and out 과 같은 독립적인 방식으로 전환해야 합니다.
  • 동일 뷰라면 뷰가 화면을 가로지르는 방식으로 전환해야 합니다.

이 차이는 인터페이스의 상태 변화의 방식을 결정하므로 매우 중요합니다. <View identity의 핵심>

동일한 ID를 공유하는 뷰다! → 동일한 element를 서로 다른 상태로 갖는다는 것.

뷰가 다르다는건 그 반대개념입니다!

  • View Identity
    • 앱의 요소를 업데이트하기 위해 동일한지 다른지를 인식하는 방법
    • 데이터 및 업데이트 주기에 영향을 미침
    여기서 앱의 요소는 View나 State를 의미함.이 id를 통해 뷰, 데이터의 업데이트 주기에 영향을 끼치는 역할을 함.
  • UIKit의 경우에는 class(참조 타입)로 생성하기 때문에 뷰 할당 시 생성되는 포인터가 해당 뷰의 명시적 id가 될 수 있지만 SwiftUI는 struct(값 타입)이기 때문에 해당 개념이 도입된 것임.

Identity는 두 종류로 나뉘게 되는데 아래와 같습니다.

View Identity의 종류

  • 명시적(Explicit) Identity - 말 그대로 명시적(사용자 정의)으로 추가하여 구분

Explicit Identity의 가장 익숙한 예는 UIKit의 포인터 ID입니다.

UIKit에서는 Class를 이용하기 때문에 각 고유 포인터가 있고, 이를 통해 뷰를 구분할 수 있습니다.

 

하지만 SwiftUI는 Struct를 사용하여 value type(값 타입)이므로 ID로 사용할 값이 없습니다.

 

 

따라서 SwiftUI는 다른 방법을 통해 명시적으로 ID를 지정해주어야 합니다.

그 예로 ForEach 구문 사용시 id 매개변수를 명시적으로 입력하는 것이 있습니다.

해당 ID 값을 이용해 view를 명시적으로 식별할 수 있습니다.

이를 통해 변경 사항을 파악하고 목록 내에서 올바른 애니메이션을 생성합니다.

 

또 다른 방법으로는 id(_:) modifier가 있습니다.

custom modifier를 생성하여 뷰를 명시적으로 식별할 수 있습니다.

이 경우 모든 뷰를 명시적으로 식별할 필요가 없이 참조해야 하는 뷰만 식별할 수 있다는 장점이 있습니다.

 

그러나 명시적으로 식별하지 않은 뷰에 대해서 identity가 없다는 것은 아닙니다.

명시적이지 않더라도 모든 view는 identity가 존재하며 이는 Structural Identity 입니다.

  • 구조적(Structural) Identity - 뷰 계층 구조를 이용해 type 및 위치에 따라 암시적 ID 생성

SwiftUI는 뷰 계층 구조를 이용해서 암시적 ID를 생성해줍니다.

→ 사용자가 모든 뷰의 id를 생성할 필요가 없음

SwiftUI는 API 전체에서 구조적 ID를 활용하는데 가장 큰 예는 View 내에서 if문과 같은 조건부 논리를 사용하는 경우를 들 수 있습니다

조건문의 구조는 각 뷰를 식별하는 명확한 방법을 제공함!

첫 번째 뷰는 조건이 참일 경우, 두번째 뷰는 조건이 거짓일 경우에만 표시가 되는데

이는 SwiftUI가 현재의 view가 위치를 유지하고 변경하지 않을 것을 정적으로 보장할 때만 작동함!

이후 뷰 계층 구조의 type을 확인한 후 작업을 수행함

 

위와 같은 코드는 내부적으로 다음과 같이 동작합니다.

if문은 참, 거짓의 content를 가지는 제네릭 타입인 _ConditionalContent로 변환이 됩니다.

이는 result builder 타입@ViewBuilder에 의해 수행이 되며 View 프로토콜에서는 body 프로퍼티를 ViewBuilder에 암시적으로 래핑하여 따로 명시하지 않아도 된다고 합니다.

 

이러한 제네릭 타입을 이용해서 배후에서 AdoptionDirectory는 true view이고,

DogList는 False view임을 보장하여 암시적이고 안정적으로 ID를 할당할 수 있도록 합니다.

마지막으로 아래 코드를 살펴보도록 하겠습니다.

상단의 코드는 각 조건 분기에 따라 서로 다른 view를 정의하고 있습니다

if 문의 각 분기가 고유한 ID를 가진 다른 view를 나타낸다고 이해하므로 뷰는 fade in-out 방식으로 애니메이션이 동작하게 됩니다.

하지만 하단과 같이 레이아웃과 색상을 바꾸는 동일한 ID를 가지는 뷰로 구성을 하게 된다면

부드럽게 미끄러지는 것(smoothly slide)을 확인할 수 있습니다.

이는 동일한 ID를 가진 단일 view로 구성되었기 때문입니다!!

두 방법 모두 동작하지만 일반적으로 두 번째 방법이 권장됩니다.

→ ID를 유지하고 유연한 전환을 제공하도록 노력하라고 하네용 ㅋㅋ

이 방법을 통해 뒤에서 학습할 view의 수명과 상태 보존에도 도움을 줄 수 있습니다.

 

찐찐막으로 AnyView 사용을 지양해야 합니다.

AnyView를 사용하게 되면 다양한 단점들이 존재하는데 다음과 같습니다.

  1. 코드를 읽기 어렵고
  2. View 추론을 런타임 시점에 진행하므로 에러나 경고를 알 수 없고
  3. 성능 저하를 발생시킬 수 있다!!@@@!!!!1

그래서 가능하다면 AnyView의 사용을 피하는 것이 좋습니다!

AnyView 대신 제네릭을 이용해 static type의 정보를 보존할 수 있도록 합시다!

 


Lifetime

앞에서 SwiftUI가 view를 어떻게 식별하는지 알아봤자나여?

여기저는 이제 IDview와 data의 수명과 어떻게 연결되는지 알아보도록 하겠습니다!!!!!

시간의 흐름에 따라서 view는 lifetime 동안 다양한 state를 가질 수 있습니다.

여기서 중요한 것은 state가 변했다고 하더라도 그 view는 view 그 자체입니다.

동일한 ID를 사용하게되면 시간이 지남에 따라 다양한 값을 가질 수 있으나 동일한 view라는 의미입니다.

 

동일한 ID라면 시간이 지남에 따라 새로운 값들이 생성되거나 state가 변경될 수 있지만 동일한 ID를 유지한다면 동일한 view입니다.

이때 중요한 것은 View value ≠ view identity 라는 것입니다.

view value는 일시적이므로 lifetime에 의존해서는 안됩니다. 이는 ID를 통해 통제할 수 있습니다.

SwiftUI는 view가 처음 생성되고 나타날 때 앞서 소개한 방법을 통해 ID를 할당합니다.

이는 시간이 지남에 따라 업데이트되고 새로운 value가 생성될 수 있지만 여전히 동일한 view입니다.

(계ㅔㅔ속 말했지만 동일한 ID를 유지하고 있기 때문에)

view는 ID가 변경되거나 뷰가 제거될 때 비로소 수명이 종료됩니다.

⇒ view의 lifetime은 id의 지속시간에 관계되어 있다!!

A view’s lifetime is the duration of the identity

 

view의 ID와 lifetime을 연결할 수 있는 것이 SwiftUI가 state를 유지하는 방법을 이해하는데 있어서 필수적입니다.

이제 StateStateObject를 통해 알아보도록 하겠습니다.

SwiftUI는 view에서 State, StateObject를 발견하면 해당 데이터 조각을 뷰의 lifetime동안 유지해야 하는 것을 알고 있습니다. 다르게 말하자면 State와 StateObject는 view의 ID와 관련된 persistent storage입니다.

SwiftUI는 view가 처음 생성될 때 초기값을 이용해 State와 StateObject 에 대한 메모리에 storage를 할당합니다.

SwiftUI는 view의 lifetime동안 storage가 변경되고 view의 body가 re-evaluated되더라도 storage를 유지합니다.

 

하지만 만약 ID가 변경된다면 어떻게 될까요??

이는 위에서 봤듯이 애니메이션의 차이도 있지만 state persistence에도 영향을 미칩니다.

만약 ID가 변경되는 방식으로 뷰가 새로 생성된다고 가정해 보겠습니다.

다음 예는 structral identity로 생성되어 서로 다른 id를 가지게 됩니다.

위에서 설명했듯 view가 생성될 때에는 초기값을 이용해 state에 대한 storage를 할당합니다.

하지만 값이 변경되어 다른 분기로 진입하게 된다면 이는 id값이 변하게 되어 다른 뷰가 되는 것입니다.

이때 새로운 state의 초기값에서 새로운 storage를 생성하고 기존의 view의 storage는 deallocated됩니다.

여기서 만약 다시 첫번째 분기로 돌아오게 된다면 또 다시 새로운 storage를 생성하고 이전의 과정을 통해 생성되었던 storage는 deallocated 되는 것입니다.

 

조건문 true 분기에 생성
true는 할당 해제, false 분기에 새로 생성
false 분기 할당 해제 true 분기 새로 생성

이를 통해 ID가 변경될 때마다 state가 교체된다는 것을 확인할 수 있습니다.

한번 정리해보자면

  • view value는 일시적이다! 따라서 lifetime에 의존해서는 안된다.
  • view identity는 view persistence를 제공한다.
  • view identity를 제어해 view lifetime을 명확히 할 수 있다!
  • view의 state persistence는 해당 view의 lifetime과 연결되어 있다!
  • (State lifetime == view lifetime)
  • SwiftUI는 데이터 기반 component(예를 들면 ForEach라던가)에 Identifiable 프로톸홀을 최대한 활용하므로 안정적인 ID를 선택하는게 중요!!!
  • SwiftUI는 Identifiable을 통해 lifetime 동안 데이터를 추적할 수 있도록 안정적인 ID를 제공하는게 목적

휴우ㅜ우ㅜ우웅 힘들다 드뎌 마지막!!!

여기서는 SwiftUI가 UI를 업데이트하는 방법을 살펴보겠씁니다!

Deiepndencies

dependencyview에 대한 input일 뿐이다.

dependency가 변경되면 view는 새로운 body를 만들어야 함.

body는 view의 hierarchy(계층 구조)만드는 곳(build)임.

action은 view의 dependency에 대한 변경을 트리거함.

 

위 그림은 아래와 같이 표현할 수 있습니다.

body 부분은 초록색 계층으로 표현되어 있으며, dependency는 파란색으로 표시되어 있습니다.

 

여기서는 body 전체의 dependency만 나타나있지만 아래 그림과 같이 각 뷰에서 추가적인 dependency를 가질 수 있습니다.

(아래 그림부터는 버튼이 아닌 서브뷰라고 생각하면 됩니다)

 

하지만 또 생각해 볼것은  다음과 같이 동일한 state 혹은 data가 여러 view에 의존할 수 있습니다.

 

위 그림에서 선이 겹치지 않도록 재배열을 하면 Dependency graph라고 부르는 그래프의 모습으로 나타낼 수 있습니다.

 

SwiftUI는 이 그래프를 통해서 새 body에 필요한 view만 효율적으로 업데이트를 할 수 있습니다.

dependency에는 다음과 같은 종류가 있습니다.

@Binding, @Environment, @State, @StateObject, @ObservableObject, @EnvironmentObject 

Identity는 위에서 소개한 종속성 그래프의 핵심입니다.

모든 view는 이전에 소개한 방식으로 ID를 갖게 되고 이는 SwiftUI가 변경사항을 올바른 view로 라우팅하고 UI를 효율적으로 업데이트할 수 있는 방법입니다.

다음으로 view에서 ID의 활용을 향상시키는 방법에 대해 소개하겠습니다.

Explicit Identity

Identifier stability

앞서 언급했듯 view의 lifetime은 ID의 지속시간이므로 ID의 안정성이 중요합니다.

따라서 안정적인 ID를 갖는것은 성능에 도움이 됩니다!!!

ID가 안정적이라면 의존성 그래프의 업데이트가 필요없고, view에 대한 storage를 지속적으로 생성할 필요가 없음. 또한 lifetime를 이용해서 persisted storage를 관리하기 때문에 안정적인 ID를 통해 state loss를 방지할 수 있음.

아래의 두가지 예에서 ID들은 안정적이지 않음.

첫 번째 예는 데이터가 변경될 때마다, 두 번째 예는 데이터의 위치에 따라 ID값이 변경되기 때문임.

따라서 실행 결과를 보면 약간의 문제가 있는 것을 확인할 수 있음(뷰가 버벅이거나 겹쳐보이는 등)

 

따라서 아래와 같이 databaseID 등의 안정적인 속성에서 파생된 안정적인 ID를 사용해야 함

 

안정적인 식별자가 좋은 식별자라고 지금까지 소개했는데 좋은 식별자로서의 또 다른 조건은 고유성임!!

Identifier uniqueness

각 ID는 단일 view에 매핑되어야 함.

이를 통해 애니메이션 효과, 성능, 계층 구조의 종속성의 효율성 등의 문제를 해결할 수 있음

첫 번째 이미지에서 id로 사용한 name은 고유하지않음(중복 가능하니깐)

따라서 좋은 id라고 볼 수 없음

두 번째는 serialNumber을 id로 사용하여 고유성을 지킴

 

지금까지 명시적으로 ID를 설정 할 때 고려할 것을 살펴보았는디 이제는 구조적으로 ID를 제공할 때 고려할 내용을 살펴보도록 하게씀

Structured Identity

위에서도 몇번 살펴봤지만 조건문 분기를 통해 View가 나뉘어지는데 이 때문에 각 view는 서로 다른 id를 가지게 되므로 다른 view로 인식이 됨.

 

그럼 하나의 id를 이용해서 하나의 view로 취급해 사용하고 싶으면 어떻게 홰야할까요?

분기(branch)를 없애고 modifier 내에서 값을 조절할 수 있도록 해주면 됩니다!

 

만약 분기(branch)가 필요없이 늘어난다면

  • 성능 저하
  • 예상치 못한 애니메이션
  • state 손실

등의 문제가 발생할 수 있음.

따라서 branch가 필요하다고 생각된다면, 여러 view를 나타낼 것인지 아니면 동일한 view의 두 가지 상태를 나타낼 것인지 판단이 필요함

진짜 끝 마무리 정리!

  • identity는 성능을 향상시킬 수 있는 방법 중 하나이다.
  • 명시적, 구조적 identity를 활용해 앱을 개선할 수 있다.
  • identity를 이용해서 lifetime을 명확히할 수 있으며, lifetime과 관련된 storage, transitions 등을 제어할 수 있다.
  • identity와 lifetime을 사용해 dependency를 형성, dependency는 UI를 효율적으로 업데이트할 수 있는 그래프로 표현한다.

몬가.. wwdc 영상으로 공부를 해본건 처음이었는데.. 갱장히.. 좋네여..

설명을 엄청 자세하게 해주니까 이해도 잘 되고 진작에 왜 안했찌 생각도 들었달까..?

그래도 여전히 여러분 부분은 많았지만 도움이 정말정말 많이 되는거 같았음

여러분들도 공식문서랑 영상 진짜 진짜 애용하세여 개꿀인듯?

구글에 문서 검색하면 이미 정리한 블로그도 많아서 같이 보면 이해 두배로 잘됨 ㅋㅋ 굿!!!

ㅋㅋ 근데 이렇게 정리하면서 들으니까 한나절 걸리네요 증말루~ 그만큼 이해를 한거겠지..?

사실 정리안하면서 한번 쓰윽 본것보다는 훨씬 기억에 오래 남을듯(언젠간 까먹겠지만ㅠ)

 

아 그리고 오늘 쓰면서 느낀점 

블로그 꾸준하게 쓰시는 분들 진짜 존경합니다 ....


Demystify SwiftUI - WWDC21 - Videos - Apple Developer

 

Demystify SwiftUI - WWDC21 - Videos - Apple Developer

Peek behind the curtain into the core tenets of SwiftUI philosophy: Identity, Lifetime, and Dependencies. Find out about common patterns,...

developer.apple.com

[WWDC] Demystify SwiftUI — Identity

 

[WWDC] Demystify SwiftUI — Identity

WWDC2021 Demystify SwiftUI 중 Identity 정리글

sujinnaljin.medium.com

네카라쿠배 5개 앱으로 완성하는 iOS 앱 개발 초격차 패키지 Online.

'iOS > SwiftUI' 카테고리의 다른 글

SwiftUI Grid  (0) 2023.07.23
SwiftUI 뷰의 변화 감지  (0) 2023.07.09
SwiftUi DataBinding  (0) 2023.06.26