본문 바로가기

iOS

[WWDC 21] Demystify SwiftUI (2)

https://lidium.tistory.com/55
저번편에서 유입되셨지요?? 이번 편도 중요합니다 ㅎㅎ

세션에서는 스유의 핵심이라고 할 수 있는 세 가지 내용을 잘 다뤄줍니다.
(1) Identity, (2) Lifetime (3) Dependencies

Identity는 Explicit Identity, Structure Identity가 있다, 스유엔 identity 항상 있다. 알고 쓰자,
viewBuilder 괜히 있는것 아니다, 알고 쓰자 정도로 짧게 정리 복습을 해보았습니다.

다음 내용으로 가 볼게요!

(2) LifeTime

자, 이전 세션은 강아지였지요. 이번 세션엔 고양이입니다. 이름은 Theseus!

고양이가 annoying 할 수도 있고, being a proper cat일 수도 있고 (암요 그럼그럼)
그래도 고양이는 하나인 거겠지요.

이 개념이 역시나, Identity와 lifetime을 잇는 본질이라는 것입니다.

중요한 사진 한 장인것 같지요? 고양이를 담는 틀은 변하지 않았고 (보라색이 유지됨)
그런데 고양이 표정이 계속 바뀌고 있다는 것을 캐치하기 바랍니다.

요 개념은 시간이 경과함에 따라 continuity를 도입할 수 있게 되는것이라고!

자, 스유에 적용하면

Single View는 각기 다른 state를 가질 수 있습니다. 이건 뷰가 바뀌는걸까요? 그렇지 않다라는 것! 뷰의 값만 바뀌는 거에요.

자, 코드로 설명해 볼게요

고양이가 배고파서 울거라고 합니다. 처음에는 25의 강도로 울고, 점점 배고파지니까 50의 강도로 운대요.

자, 여기서 body 클로저에서 같은 뷰 정의에 의해 PurrDecibelView가 선언이 됩니다.
스유는 이전 복사된 값을 잠시 살려두는데요, 새로운 뷰와 비교하고, 아 새로운게 생겼군! 하고 판단 뒤
기존의 복사된 값을 지워버립니다.



자, 명심해야 할 것은
viewValue가 바뀐것이지요? 그럼 view가 바뀐것일까요? 그렇지 않다는 것이에요.
view Value != view Identity. 아까 고양이 그림 기억하세요 리멤버

view Value는 굉장히 수명이 짧아요. 혼동하지 말길 바라고요!

기존 논의에 따라 PurrDecibelView에 어떤 identity가 부여되었겠죠?
그리고 intensity가 계속 변화합니다.. 고양이님 니즈가 매번 다르니까. 그렇다고 해서,
매번 고양이가 새로 생성될 수는 없는 것과 같은 이치겠어요. 스유의 관점에서, 얘는 같은 뷰!

그럼, view의 lifetime은 중요하잖아요, 얘는 언제 없어지는 것일까요?
view의 identity는 꼭 하나씩 존재하지요, identity가 교체되면, view도 교체되는 것입니다. (onDisappear)

잘 이해되셨지요? 다음엔 코드로 설명할겁니다.


@State, @StateObject와 view lifetime을 연관지어 설명해 보겠습니다.
SwiftUI는 view를 볼때, State, 또는 StateObject를 보게 되는데요, 이는 view의 lifetime동안 지속되게 됩니다.
다시 말해서, state 또는, stateObject는 해당 view의 identity와 연관되어 사용하는 영구 저장소인 것입니다.

요 어노테이션은 나중에 글 써서 연결할게요! 잘 모르시겠다는 분은 viewModel이라고 생각해 주세요
외부에서 뷰에 주입해주는 겁니다!

자, CatRecorder가 메모리에 오르면, title와 mic도 초기값을 이용해서 메모리에 올라가게 됩니다.

요런 true / false branch가 있어요. 저번시간 안봤으면 봐야합니다!

이제 알 수 있지요? true / false branch는 각각의 identity를 가진다, 이는 viewBuilder의 영향이 있고요,
따라서 여기서 dayTime, nightTime이 바뀌면 어떻게 되지요?

당연히 identity가 바뀌니까, View가 새로 생기는 겁니다. 새로 생기고, 사라지고! (저번 시간에 배운 내용)
그럼 다시 돌아와서,
해당 코드에서는 true / false branch가 바뀌면, State도 변경되게 되는것이겠죠. (아주 중요, 이건 안됨)

또. SwiftUI에서 Data-driven construct로 제공해주고 있는것이 위와 같은데요,
Foreach를 예를 들어서 봐볼게요

forEach는 각 Text마다 identity를 부여해주고 있는데요,

올해부터는 이런 타입을 워닝을 내 준다고 합니다.
그럼 Foreach가 완전 쓸모 없어진 것이 아닌가요!? 라고 할 수 있지요, 그러나 구조적 개선이 있었어요.

sheeps는 dynamic range이면서 identity를 부여할 수 있는지 없는지 알 수 없거든요, 그래서

UUID처럼, String이나 Id처럼 Hasable한 값을 제공하게 되면 괜찮습니다.
그럼 각각의 Foreach element마다 identity를 가지게 되니까요!
따라서 SwiftUI가 얘가 어떤 뷰인지 인식할 수 있게 됩니다.

아까 위에서는 KeyPath로 identity를 알려줬잖아요? 귀찮죠..!
이땐 Identifiable 프로토콜을 채택하면 되겠습니다.

Foreach를 잠깐 까보면

두번째 줄, Content가 View이며, Element가 Identifiable이어야 하는것 보이시나요?
따라서 관련되어 ViewBuilder 프로퍼티를 통해 연관된 Content를 리턴하게 된답니다.

그럼 이제 SwiftUI가 View lifetime동안 뷰의 변화를 트래킹 해줄 수 있다는 것!
Stable한 Identity 제공은 정말 중요. 알고 사용하는것 중요.


LifeTime 정리 : *정독*

(3) Dependency

자 그럼, 이번에는 SwiftUI가 어떻게 뷰를 업데이트하는지에 대해서 알아볼거에요.

3번 컨셉은 다른 진행하시는 분이 맡았는데, 이분은 Dog-Person이라고 다시 Cat에서 Dog으로 돌아왔습니다 ㅎㅎ
간단한 뷰가 있습니다

프로퍼티 2개가 있고, dog가 있고, treat가 있어요.

요 2개의 프로퍼티들이 바로 dependency.
캡쳐 잘 했네요! dependency는 view의 input일 뿐입니다.

dependency의 값이 바뀌면, new body가 생기게 되겠습니다.

요것은 DogView의 Diagram이구요, Button을 누르면 reward를 주는 형태이지요.

button을 누르면, reward 메소드가 발동되어서, 해당 뷰에 있는 dependency 값을 교체하게 됩니다.
그럼!? 뷰를 다시 그리게 되는 것이지요. 자, 오해의 소지가 없어야 해요.
view의 body를 새로 그린다는 것은, view의 lifetime이 끝났다는것이 아니라는거 다시한번 짚고 감니다!

자 여러분, 이 Diagram을 단순화 시켰을때, 이친구는 어떠신가요?
굉장히 tree 같지요? (아니라는건 아님)

네, 그런데 이 아래 계층에 있는 view들도 각각의 dependency를 가질 수 있지요. (여러 State가 있으니)
그리고, 부모의 dependency도 아래 뷰가 직접 받을수도 있구요!

요걸 이제, 요리조리 잘 풀어 보겠습니다. 그러면 아래처럼

짜잔
tree가 아닌 graph 구조가 나오게 되네요

요 그래프가 왜 중요하냐면,
맨 아래부터 보았을때 (파란색 : dependency) (초록색 : view)
맨 아래의 dependency가 바뀌었을때, 연결된 하단 두개의 view만 새로 그려지게 됩니다.

자,

그런데 이 왼쪽 상단 뷰가 변경되었을 땐, 우측 상단 뷰도 영향을 받지요
그런데 이건 view가 Struct인 특성상 비교가 아주 간단하고, 가볍다는 것이에요

변경점이 있어야 한다면 아래 계층에 뷰 변경을 전달하고, 아래 계층 또한 새로 그려지게 되겠습니다
요런 점이 있어서, main ContentView의 body가 계속 다시 그려지는것이 아닌, 필요한 것만 새로 그려지는 메커니즘이 가능한 것

위에 언급한 내용들을 위해서 SwiftUI에선 요렇게 다양한 프로퍼티들을 제공하고 있다고!


여러분 여러분 거의 끝나가요. 아래는 복습입니다!

이런 코드가 있구요, 아하 그럼 Pet은 Identifable을 채택하니 괜찮겠군!
이라고 읽으셨다면 안되겠어요

요 위에 방법은 불완전한 identity에요. 따라서 뷰가 업데이트 되면 화면이 깜빡이는 모습이 보이게 된답니다.
stable Identity를 위하여 저기 id쪽을 봐 봅시다

UUID가 계속 재생성되니까요, 문제가 있지요.

저기 id값은 각각의 객체마다 고유의 값이여야 하는것이니까요, databaseID 등 stable한 고유의 값을 identifier로 사용하도록 합니다.
자아아아 마지막 예제

자 다왔습니다
요건 된다 안된다???!!!
name이 같은 먹이들이 있을 수 있잖아요!? 그래서 버그가 있을 수 있겠어요
따라서 아주 stable한 identifier를 잘 골라서 적용해주자

결론 : UUID 사용할때 주의. 해당 객체가 가지는 고유 값을 identifier로 사용하도록 하라


요렇게 정리가 됩니다.
몇가지 정리가 필요합니다! 3가지 주제에 대해서 다뤄보았는데,
(1) Identity - 모든 뷰는 identity가 있고, explicit 또는 implicit identity가 있음. if / else 주의
(2) LifeTime - view의 값이 변경될때마다 view의 lifetime이 끝난다? ❌ view의 lifetime은 view의 state와 밀접관련이다 ⭕️
(3) Dependency - 뷰 내부의 프로퍼티가 있고, 프로퍼티가 변경될 때 마다 view hirachy를 따라 render를 다시 한다, 이때 stable Identity는 매우 중요하다!

고생하셨습니다. 그런데 정말 가치있어요.
SwiftUI를 작성하면서 애니메이션이 예상대로 될 때가 있고, 아닐때가 있었는데
그 이유를 명확히 알 수 있었거든요. 스유 세션들 다 훌륭하지만 요건 아주 중요한 것 같네요.

(잘 정리해뒀지만) 직접 세션을 보는것 아주 추천드립니다. 이만 마치겠습니다~!
감사합니다!