얼마전에 WWDC 관련 영상 보다가
스유 관련 블로그 글도 추천받았는데요.
그 중 하나가 Medium에 있는 Under the hood: SwiftUI 였습니다.
https://medium.com/ios-gems/under-the-hood-swiftui-93ae7f0ae830
Under the hood는 그 이면의, 겉으로는 드러나지 않은 내부 동작을 말하는데요,
이 블로그에서는 SwiftUI가 내부적으로 어떻게 동작하는지,
9가지 주제로 이야기 하고 있습니다. (인터뷰 형식)
스유에 대해서 조금만 알고 있어도 어느정도 이해할 수 있는 수준의 내용이었구요.
제가 이해한 언어로 풀어서 정리를 해보겠습니다.
인트로에서는 우선 SwiftUI는 "UI를 만드는 간단한, 선언적 프레임워크"라고 소개합니다.
하지만 그 내부에는 정교한 절차가 숨겨져 있다고 하는데요.
여기서 SwiftUI의 특징을 먼저 짚고 넘어갑니다.
1. 선언적 도메인 특화 언어 (DSL): 스유에서는 뷰를 트리 구조로 정의합니다.
2. State-driven rendering: 스유에서 뷰 내부에 있는 state (데이터의 상태, 변화라고 생각하면 될 것 같습니다) 의 변화를 중심으로 뷰가 재생성됨으로써 업데이트 됩니다. 즉 데이터의 변화에 자동으로 뷰가 업데이트 된다는 것이죠. 이때 이 재생성을 담당하는 것이 'reconciliation system' 이라고 하는데 조화, 화해 시스템이라고 번역됩니다. 앞으로 자주 등장하는 용어입니다. 변화를 반영해서 다시 뷰를 업데이트하는 역할을 합니다.
3. Backed by UIKit/AppKit: 스유로 작성된 코드는 내부적으로는 UIView로 변형되어서 동작합니다. 스유와 기존의 UIKit을 이어주는 것을 UIHostingController라고 하는데 이에 대해서는 뒤에 더 나옵니다.
4. Swift 언어 특성에 의존: 스유는 Swift 언어가 갖고있는 여러 기능, 특징들을 사용합니다. result builder, generic, property wrapper가 그것입니다. 이는 스유에서 뷰를 업데이트하는 등에 활용됩니다.
이 정도가 스유의 대표적인 특징이라고 할 수 있습니다.
간단하게 스유의 내부 동작 과정을 설명하면,
1) state에 변화를 반영하여 body 함수를 재실행하고,
2)이전의 view 트리와 새로 만드는 view 트리를 reconcile(조화시키며)하며,
3) 최소한의 diff(차이)만 적용하는 것이 스유 내부의 동작입니다.
가장 자주 등장하고 중요한 키워드를 뽑으라면, state, reconcile, diff 인 것 같습니다.
1. SwiftUI’s Declarative Approach and View Protocol
스유의 선언적 방식과 뷰 프로토콜
스유의 성능의 열쇄가 단명하는 방식 이라고 말합니다.
스유의 뷰 객체는 메모리의 큰 전체 뷰 트리를 저장하고 있지 않으며,
그때그때 뷰가 어떻게 보여야하는지 명령하는 것에 가깝다.
(이런 명령을 받으면, 내부적으로는 이전의 트리와 비교해서 차이점을 반영하는 방식)
이건 선언형 언어의 특징이기도 합니다.
명령형 언어는 어떤 일에 대한 절차를 하나하나 명령해주는 방식이라면,
선언형 언어에서는 결과, 목표, 지향점을 선언하기만 하고, 그 내부에서 알아서 동작이 처리되도록 하는 것이니깐요.
선언형 언어는 명령형 언어의 추상화된 버전이 아닌가 하는 생각도 듭니다.
2. Reconciliation (Diffing) Behind the Scenes
뷰 트리에 무언가 변화가 생겨서 뷰를 재생성 (re-create, re-render) 해야할 때
내부 diffing 엔진이 작동합니다.
이 친구는 이전의 뷰 트리(old tree)와 새로 만들고 있는 뷰 트리를
노드 단위로 비교하면서 diff(차이)가 무엇인지 식별하고,
그 차이점에 대해서만 re-render해서 효율적이고 빠르게 뷰를 업데이트함.
여기서 좀 더 구체적으로 설명하면, 어떤 노드에서 값의 차이가 탐지되면,
그 노드를 기준으로 서브트리를 재생성 재랜더링하는 방식인듯.
3. State and Property Wrappers Internals
앞에서 말한 상태를 관리하는 것 (state management)을 위해
스유를 써본 사람이라면 봤을 여러 프로퍼티 랩퍼(rapper아닙니다)
@State @Binding @ObservedObject @EnvironmentObject 를 사용하는데요.
각각이 어떻게 쓰이고 어떤 역할을 하는지 간단히 설명합니다.
@State
스유가 추적하는 '값' 입니다.
즉 @State가 붙어있는 프로퍼티는 스유가 내부적으로 추적감시하고 있다는거죠.
변하나 안변하나, 변하면 즉각 대응하려고
이 값이 바뀌면 그에 해당하는 서브트리를 re-render하라는 신호로 받아들입니다.
@State 프로퍼티는 SwiftUI Cash 내부의 주소값이기도 합니다.
(State로 프로퍼티를 지정하면, 그 변수는 스유 캐시에 들어가고, 해당 프로퍼티는 포인터마냥 그 주소값을 갖고 있는것,
GPT한테 물어보니까 @State는 구조체로 구현되었지만 참조타입처럼 동작한다고 함)
깨알 지식
(@State @Binding -> 구조체로 구현 but 참조타입 처럼 동작,
@ObservedObject @EnvironmentObject -> 참조타입 (클래스 인스턴스를 갖고있대..))
@Binding
다른 곳 (코드가 위치한 타입 외부) 에 저장된 @State를 가리키는 포인터처럼 동작.
Binding 프로퍼티 값을 변경은 원래 값 @State 값을 변경하는 것임. (걍 포인터임)
보통 child 뷰가 부모 뷰의 변수를 이런식으로 전달받아서 사용함.
기능: 해당 변수의 get, set 가능 (읽기와 쓰기 모두 가능)
@ObservedObject
ObservableObject 프로토콜을 준수하는 (외부) 타입의 관찰자. ..
published 프로퍼티가 변경될 때 마다 퍼블리셔 발행
(아직 완전히 이해하지 못한 이슈로 설명은 여기까지)
@EnvironmentObject
뷰 계층에서 자동으로 pass되어 내려온 환경변수
이건 뷰를 dismiss할 때 한번 사용해봤는데,
전역변수라고 생각하면 좋을 것 같다. 앱 전반에 걸쳐 사용되는
이것도 뒤에 더 나옴
스유가 이런 프로퍼티 값들의 최신값을 갱신하면서 body뷰를 재 실행한다. (re-render)
즉 이런 값들의 변경 = 스유 뷰 랜더링 트리거
이로인해 변화하는 값에 따른 뷰 업데이트가 자동적으로 수행됨
4. The Role of Environment and Inheritance
Environment와 상속의 역할
Environment value는 바로 전에 나왔듯이 스유에서 전역변수처럼 작용하는 변수이다.
서브뷰에 하나하나 넘겨주어야 하는 수고를 줄일 수 있도록 만들어짐.
예로는 Color scheme, layout direction, accessibility setting 등이 있음.
Environment와 관련된 내부 동작과정은 아래와 같다.
1. 뷰 트리 계층의 각 레벨에서 부모 노드로 부터 받은 environment struct를 유지하고 있다.
2. 자식 노드에서 .environment(__, __) 를 사용해서 environment를 override하는 것이 가능함.
3. reconciliation때(뷰 트리 재조립, 재구성 정도로 해석) 부모의 environment 가 바뀌면 그의
서브트리들을 re-rendering한다.
-> environment의 변경이 그걸 읽는 subview만 re-render하게 해서 효율성을 높이는데,
여기서 읽는 다는건 그 environment value를 참조하는 것을 말하고,
environment value를 참조하는 sub view가 무엇인지는 컴파일때 다 정리해둠.
-> 런타임시 오버헤드 줄임
5. Integration with UIKit (UIHostingController)
스유와 UIKit을 연결하는 것: UIHostingController
둘 사이의 다리역할을 하는 모듈이다.
하는 일
- 모든걸 스유로 개발하면 UIHostingController가 root view controller를 만들어서 내부적으로
스유의 뷰 트리 계층을 UIKit 뷰로 변환한다. 이로써 UIKit과 동일하게 동작하게 함.
- UIKit 기반에 스유를 추가하면, 개발자가 UIHostingController를 만들어서 둘을 연결해야함.
(UIKit 루트 뷰컨에 스유로 만든걸 자식뷰로 추가함) - UIHostingController를 추가하는건가
6. Layout System: The Proposed/Size Model
스유 레이아웃 모델은 UIKit의 auto layout과 다른 "propose-measure-place" 패턴이다.
스유 body 뒤에서는 'sizeThatFit' 함수가 배치를 담당하는데
동작과정은 아래와 같다.
1. 부모뷰가 자식뷰에게 이용가능한 공간을 알려줌 (propose)
2. 자식뷰가 그 공간 내에서 본인이 필요한 크기를 측정하여 반환함 (measure)
3. 부모뷰가 자식뷰를 뷰에 배치함 (place)
예를 들면, HStack에서 자식 뷰들의 크기를 측정해서, 더해서, 배치하는 프로세스 (..)
스유와 UIKit의 차이점은
스유는 single-pass 협상이라는 것 즉 동등한 레벨의 constraint가 협상하는 것이 아니라
다소 수직적인 협상인듯. 빠르고 결정론적이지만, 고급 동적 레이아웃을 하기엔 UIKit보다 더 많은 수작업이 필요할 수 있다고 한다.
ex. geometry reader, preference key 등.. 아직 이들에 대해 깊이 알지 못함
7. Performance Considerations
제목은 대충 스유를 둘러싼 성능 이슈. 로 해석하면 될 것 같다.
스유의 성능적 장점은
- 생산성 증가 (ex. 뷰 업데이트 자동화)
- 데이터 동기화 보장
- 컴파일시 뷰 조합 (런타임 오버헤드 줄임)
스유의 성능적 단점
- 잦은 재실행 재계산 재랜더링 -> 업데이트시 전체뷰를 어쨌든 다시 만듦 (오버헤드 유발)
- body 로직이 무거워질 수 있음
(그래서 복잡한 로직은 외부로 오프로딩하길)
- 과한 애니메이션은 문제를 발생시킬 수 있음 ..
8. Testing and Preview Mechanics
프리뷰 매커니즘! 스유는 프리뷰를 제공한다.
내가 작성하는 스유 코드에 따라서 실시간으로 UI를 보여줌
이것의 내부 동작 과정은..xcode에서 mini host 환경을 실행함으로써 가능해지는데,
이 내용은 완전히 소화했다고 보기 어려움
1. 스유 코드를 동적 라이브러리로 컴파일한다.
2. xcode 캔버스에서 시뮬레이터 같은 처리를 수행함
3. 실시간으로 랜더링함
단, 로직이 복잡해지거나, 의존성이 너무 많아지면 프리뷰가 어려울 수 있음
하지만? 프리뷰를 가능하게 하면 그만큼 생산성이 올라간다~
9. Limitations and Edge Cases
드디어 마지막 주제
스유의 한계점
1. iOS 13이상에서만 사용가능하다.
2. drawing feature은 아직 UIKit에 비해 미비하다.
3. 아직 UIKit과 동등한 수준의 기능을 갖추진 못했다.
3. 러닝커브 존재
4. re-render strom을 방지하기 위해, 큰 데이터 셋을 다룰때는 유의해야한다.
Final Thoughts
마무리 글을 어떻게 하면 스유를 마스터할 수 있는지, 조언을 모아둔 것이었다.
- 애플이 제공하는 샘플 코드로 공부해라, 그게 best practice다.
- 리액트, 플러터 처럼 다른 선언적 프레임워크와 개념이 유사하니, 그것들을 읽어봐도 좋다.
- Instruments를 사용해라. 내 코드의 상태, 메모리 누수는 없는지, 랜더링은 얼마나 자주 일어나는지 등
- Concurrency랑 같이 사용해라 (스유는 UI 담당, Concurrency(async)는 비동기 데이터 변화 반영)
- Environment: custom property wrapper, result builder를 시도해봐라. 전형적인 뷰에서 벗어나보기!
스유에 대해서 아직 잘 알지는 못하지만
배워가고 입장에서 내부 동작을 아는 건 꽤 도움이 많이 되는 것 같다.
유익한 블로그이니 참고하면 좋을 것 같음
https://medium.com/ios-gems/under-the-hood-swiftui-93ae7f0ae830
Under the Hood: SwiftUI
This interview is part of the Under the Hood series. Each discussion dives deep into advanced iOS and Swift concepts, peeling back the…
medium.com
'iOS' 카테고리의 다른 글
[iOS] 프로토콜 지향 프로그래밍 (POP) (0) | 2025.04.02 |
---|---|
[iOS] Network Calls (네트워크 호출) (0) | 2025.03.28 |
[iOS] View Life Cycle (UIKit) (0) | 2025.03.28 |
[iOS] Delegate & Protocol (0) | 2025.03.26 |
[iOS+@] Concurrency (동시성) (0) | 2025.03.25 |