Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Demystify SwiftUI #16

Closed
samsung-ga opened this issue Jun 29, 2022 · 0 comments
Closed

Demystify SwiftUI #16

samsung-ga opened this issue Jun 29, 2022 · 0 comments

Comments

@samsung-ga
Copy link
Collaborator

samsung-ga commented Jun 29, 2022

Demystify SwiftUI

스크린샷 2022-06-29 오후 3 45 47

SwiftUI는 선언형 프레임워크로, 앱에 대해 원하는 것을 높은 수준에서 작성하면 SwiftUI가 이를 실행하는 방법을 결정한다. 이는 정말 마법처럼!! 엄청나다고 느껴질 수 있다.

하지만, SwiftUI가 예상치 못한 일을 할 때가 있다. 그 순간에 SwiftUI가 원하는 결과를 얻는 방법에 대한 더 나은 이해를 위해 scene 뒤에서 무엇을 하는 지 아는 것이 중요하다.

이 영상은 **SwiftUI가 코드를 볼 때 무엇을 보는가?**에 대한 답으로 세가지를 소개한다.

  • Identity
  • Lifetime
  • Dependencies



Identity

Swift는 identity로 View들을 구분한다. 먼저, SwiftUI에서 사용하는 두 가지 identity에 초점을 맞춰 코드에 어떻게 보여지는 지 살펴보자.

Explicit identity

  • 커스텀 또는 데이터 기반 identifier이다.
  • 즉, 강아지를 예로 들어보면, 이름이 같은 경우 같은 강아지, 이름이 다른 경우 다른 강아지라고 이름을 통해 보장할 수 있다.
  • 이와 같이 이름이나 식별자를 지정하는 것이 일종의 explicit identity이다.
  • 하지만 이러한 이름이나 식별자를 어딘가에 저장해두어야 한다.
    • UIKit이나 AppKit에선 pointer를 이용하여 explicit identity를 나타냈다. 포인터를 사용하여 개별 뷰를 참조할 수있으며 두 뷰가 동일한 포인터를 공유하는 경우 실제로 동일한 뷰임을 보장할 수 있다. (뷰가 class이기 때문)

스크린샷 2022-06-29 오후 3 38 42


스크린샷 2022-06-29 오후 3 52 00


  • 하지만 SwiftUI 뷰는 struct이기 때문에 pointer가 explicit identity를 가질 수 없다.

Views as value types in SwiftUI

  1. 할당되지않기 때문에 포인터가 없다.
  2. 효율적인 메모리 관리
  3. 작고 한가지 목적의 컴포넌트 지원
    자세한 이야기는 SwiftUI essentials WWDC19에서 볼 수 있다.

그렇다면 View in SwiftUI는 어떻게 identity를 가질 수 있냐!


  • ID 부여하는 방법 (dogTagID 부여)
  • 다른 section 사이를 이동하는 뷰를 애니메이션할 수도 있다.
List {
    Section {
        ForEach(rescueDogs, id: \.dogTagID) {
            ProfileView($0)
        }
    }
    Section {
        ForEach(adoptedDogs, id: \.dogTagID) {
            ProfileView($0)
        }
    }
}

  • id(_:) modifier를 이용해 커스텀 identifier 제공하는 방법
  • (장점) 모든 뷰를 explicity하게 식별할 필요가 없고 헤더 텍스트와 같이 코드의 다른 곳에서 참조해야하는 뷰만 식별하게 할 수 있다.
ScrollViewReader { proxy in
    ScrollView {
        HeaderView(rescueDog)
            .id(headerID)
        Text(rescueDog.backstory)
        
        Button("Jump to Top") {
            withAnimation {
                proxy.scrollTo(headerID)
            }
        }
    }
}

이 때 headerView만 id를 가진다고 다른 view들이 identity를 안 가지는 것은 절대 아니다. 모든 뷰는 identity를 가지고 있고, 이것이 explicit이 아니어도 된다.

여기서 structural identity의 개념이 나온다.

Structural identity

  • 뷰 계층 구조에서 유형과 위치에 따라 구분하는 identifier
  • 비슷한 개가 두마리 있지만 이름을 모를 때, 위치를 기반으로 그들을 식별가능하다.
  • 즉, 상대적인 위치, 배열을 사용하여 View를 구별하는데 이를 structural identity라고 한다.
  • SwiftUI는 전체 코드에서 structural identity를 활용한다.
  • 가장 기본적인 예제는 if 문과 같이 조건부 논리를 사용하는 예이다.
  • 조건부 구조는 이 identity를 확실하게 줄 수 있다.
// ✅조건이 참일 때 표시되는 View와 조건이 거짓일 때 표시되는 View
var body: some View {
    if rescueDogs.isEmpty {
        AdoptionDirectory(selection: $rescureDogs)
    } else {
        DogList(rescueDogs)
    }
}
  • 하지만 이것은 SwiftUI가 뷰가 제자리에 유지되고 절대 위치를 바꾸지 않는 정적인 경우에만 작동한다.
    그러면 보장되지 않을 경우엔???

  • 뷰 계층 구조의 타입 구조를 살펴본다.

  • 이 말은 즉 SwiftUI는 뷰를 볼 때, 제네릭 타입으로 본다.

some View가 Generic 타입으로 ViewBuilder에 의해 빌드될 때 변형된다. View 프로토콜은 body 프로퍼티를 ViewBuilder안에 암시적으로 감싸져 있다. 그래서, some View는 이 정적 복합 유형을 나타내는 자리 표시자로 코드를 복잡하지 않게 숨겨준다.


스크린샷 2022-06-29 오후 5 33 54
**영상에서 소개한 선호하는 identity** SwiftUI는 이 두가지 identity 모두 선호하지만, structural identity보다 explicity identity를 사용하는 것을 더 선호한다. 하나의 View를 분기문으로 나누게 된다면 서로 다른 고유 ID를 가진 다른 뷰이기 때문에 애니메이션이 `in and out`뿐이다. 하지만 일관된 ID로 단일뷰를 수정하게 된다면 애니메이션이 부드럽게 이루어지는 것을 알 수 있다.

스크린샷 2022-06-29 오후 5 40 22


스크린샷 2022-06-29 오후 5 40 36


AnyView

  • "타입 지우기 래퍼 타입", 즉 뷰 타입을 숨겨준다.
  • SwiftUI가 코드를 볼 때 조건 분기는 제네릭 유형 구조로 보게된다.
  • 만약 함수의 각 조건 분기에서 다른 종류의 뷰들을 반환하는 경우 swift는 전체 함수에 대해 단일 반환 유형이 필요하기 떄문에 AnyView로 모두 래핑한다.
  • 이럴 경우 SwiftUI는 코드의 조건부 구조를 볼 수 없고 AnyView를 함수의 반환 유형으로 본다.
  • 모든 조건의 반환 타입에 AnyView가 붙게 될 경우 코드가 복잡해진다.
  • 그러므로 @ViewBuilder를 붙임으로 해결이 가능하다.
// 예제 코드 삽입

일반적으로 AnyView를 피하는 것이 좋다.

  • AnyView가 너무 많으면 코드를 읽고 이해하기가 어려워진다.
  • AnyView는 컴파일러가 정적 유형 정보를 숨기기 때문에 오류를 찾기가 더 힘들어진다.
  • 필요하지 않은 경우에 AnyView를 사용하면 성능이 저하될 수 있다. 그러므로 가능하면 제네릭을 사용하여 정적 유형 정보를 유지해라.




Lifetime

이제는 뷰의 ID가 뷰와 데이터의 수명과 연결되는 지 살펴보자.

View Lifetime

  • 이름과 같은 구분자(identifier)를 통해 시간의 흐름에 따라 다른 값을 가지는 안전적인 요소를 정의할 수 있다.
  • 아래 예제에서 하나의 Element인데 그 내부의 Value는 계속 달라지는 것을 확인할 수 있다.



  • View의 value는 뷰의 identity와 다르다.
  • 즉, View의 value(값)는 일시적이므로 수명에 의존해서는 안된다.
  • 뷰가 처음 생성되고 나타날 때, SwiftUI는 뷰에 identifier를 할당한다.
  • 시간이 지남에 따라 View에 새로운 value가 할당되지만 SwiftUI의 관점에서는 동일한 View이다.
  • 뷰의 ID가 변경되거나 뷰가 제거되면 해당 수명이 종료된다.
  • 즉, View의 생명주기는 해당 View의 identity의 지속 시간이다.


Data-driven constructs

  • SwiftUI는 데이터의 ID를 View의 explicit identity로 사용하는 몇 가지 데이터 기반 구조를 가지고 있는데 대표적인 예는 아래 그림이다.

스크린샷 2022-06-29 오후 9 10 44


ForEach 예시

  • 일반적으로 일정 범위를 필요로 하며, 이 고정된 범위를 통해 View의 생명주기 동안 ID가 안정적임을 보장한다.
  • 이 때, 동적 범위를 지정하는 것은 오류이며, 일정하지 않은 범위를 지정하면 경고가 표시된다.

  • 동적 컬렉션일 경우 컬렉션과 키 경로를 ForEach의 입력으로 사용한다.
  • SwiftUI는 컬렉션의 요소를 통해 생성된 모든 뷰에 ID를 할당하기 위해 해당 값이 해시가 가능해야 한다.
  • 이 때 표준 라이브러리의 Identifiable 프로토콜을 제공한다.
  • Swift는 위 프로토콜을 활용하여 ForEach에 키 경로를 생략하고도 데이터와 View의 ID를 연결할 수 있다.

정리

  • SwiftUI의 View의 value는 View Lifecycle에 의존하지 않지만 Identity의 지속 시간이다.
  • StateStateObject의 지속성은 view의 lifecycle에 의존하지 않게 만든다.
  • SwiftUI는 데이터 기반 구성요소에 대해 Identifiable 프로토콜을 최대한 활용하여 데이터에 대한 안정적인 identity를 선택하는 것이 중요하다.

Dependency

SwiftUI가 UI를 업데이트하는 방법에 대해 살펴보자.

  • 두 가지 프로퍼티는 Dependency라고 하며 View에 대한 input이다.
  • Dependency가 변경되면 View는 새로운 body를 요구한다.
  • body는 View의 계층을 구성한다.
  • Action은 View의 Dependency에 대한 변경을 일으킨다.

DogView 코드를 다이어그램으로 그려보자.



위는 트리 형태의 구조이지만, 각 View마다 Dependency를 걸게된다면 아래와 같이 graph의 형태로 변하게 된다.


Dependency Graph

  • 위 다이어그램을 다시 그려보면 아래와 같이 표현할 수도 있다. 이를 Dependency Graph라고도 한다.

  • 이 그래프의 하단 dependency가 변경이 되었다고 가정한다.

  • 맨 아래에 있는 항목이 수정되면 연결된 두 개의 뷰만 업데이트가 필요하다.
  • 따라서 SwiftUI는 각 View의 body를 호출하여 갱신한다.

img

  • 이후 SwiftUI가 변경이 필요한 뷰만 효율적으로 업데이트한다.

정리

  • 즉, ID(identity)는 Dependency Graph의 핵심이다.
  • 모든 View는 ID를 통해 동일성을 체크하고, 이를 통해 SwiftUI가 변경 사항을 올바른 View에 효과적으로 적용한다.
  • 아래는 많은 종류의 dependency 예제이다.
// ✅ Properties of the struct
@Binding
@Environment
@State
@StateObject
@ObservedObject
@EnvironmentObject

Identifier

Identity, ID 등 View의 구분자를 의미한다. 위에서 계속 설명했던 것인데 용어가 여러개 나오네요..

좋은 Explicit Identifier

  • Stable (안전성) : 매번 바뀌면 안된다.
    • 애니메이션 개선
  • Unique (고유성)
    • 애니메이션 개선
    • 좋은 성능
    • 계층 구조의 의존관계가 효율적으로 유지

좋은 Structure Identifier

  • 불필요한 분기는 자제하자.
  • 단일 View를 위해서는 분기 대신 수정자를 사용하는 것이 더 잘 작동한다.
@samsung-ga samsung-ga added WWDC21 in progress 작업 중 and removed in progress 작업 중 labels Jun 29, 2022
@samsung-ga samsung-ga changed the title 영상 제목 영어 원문 그대로 Demystify SwiftUI Jun 29, 2022
@samsung-ga samsung-ga self-assigned this Jun 29, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants