-
Notifications
You must be signed in to change notification settings - Fork 3
[정주] WWDC23 ‐ Demystify SwiftUI Performance 정리
유정주 JeongJu Yu edited this page Jul 24, 2024
·
1 revision
- 앱이 복잡해질수록 성능 문제가 중요함
- 개발 초기부터 빠른 코드를 작성하는 법을 이해하면 앱이 복잡해질 때 생기는 문제가 줄어듬
- 피드백 루프
- 성능 문제는 증상에서 시작함
- 문제 해결의 시작은 측정임
- 그다음엔 증상의 원인을 파악해야함
- 일이 어떻게 돌아가야 하는지 직관으로 알아야 하기 때문에 어려운 단계임
- 버그는 앱에 틀린 전제가 있을 때 발생함
- 이 세션은 앱의 전제와 현실 사이의 불일치를 파악할 수 있도록 도와줌
- 최적화해서 문제를 해결
- 다시 측정, 확인해서 문제가 해결되었는지 확인
- 해결되었다면 피드백 루프가 끝난다
- 각 자식 뷰는 해당 뷰의 선조가 생성하는 뷰 값에 종속되지만 그 외에도 다른 형태의 종속성이 있음
- 동적 프로퍼티에도 종속성이 흔하게 발생함
- e.g. @Environment
- 뷰 업데이트 순서
- 뷰에 새로운 값을 생성 → 뷰에 저장된 모든 프로퍼티 포함
- 뷰의 동적 프로퍼티를 모두 업데이트
- 업데이트한 값을 이용해 body가 실행, 뷰의 자식을 생성함
- 값이 새로 바뀌거나 종속성이 바뀐 뷰만 업데이트됨
- 런타임의 뷰의 변화를 출력하려면
Self._printChanges
메서드를 사용하면 됨- lldb
expression Self._printChanges()
- 이 메서드 호출을 body에서 할 경우, 절대 App Store에 제출하면 안 됨
- 디버깅에만 사용됨, 런타임 성능에 영향을 줌
- lldb
- 업데이트를 최적화하려면
- View가 종속성을 약하게 가져야한다.
- e.g. 이미지를 표시하는 View → 전체 구조체를 전달 받는 대신, 이미지만 전달 받는다.
- 뷰를 작게 분리한다.
- Observable 프로토콜을 사용한다. → 종속성 중 읽히는 것만 자동으로 남김
- View가 종속성을 약하게 가져야한다.
- 업데이트가 적으면 성능이 좋아짐
- SwiftUI에서 View 업데이트가 늦어지는 경우
- 동적 프로퍼티 인스턴스화가 너무 비싼 경우
- 상태 객체의 할당, 초기화
- body에서 작업을 하는 경우 (body 내부의 로직이 비싼 경우)
- 문자열 편집, 데이터 필터링 등
- 보통 이 둘은 별개가 아니라 연관되어 있다.
- 동적 프로퍼티 인스턴스화가 너무 비싼 경우
- 동기적으로 진행되는 비싼 로직은 async-await과 task를 이용해 비동기로 수행
- 반복되는 Heap 할당도 줄이면 좋음
-
식별자를 이용해 데이터가 어떻게 바뀌었는지 알아낸다.
-
일관성을 위해 리스트와 테이블의 ID는 모두 즉시 수집됨
- 식별자를 빨리 생성할 수 있다면 load 및 업데이트 시간도 빨라짐
- 리스트와 테이블이 아니더라도 SwiftUI는 식별자를 자주 수집하므로 ID 생성이 빠를수록 좋다
-
식별자가 바뀌었다 → 뷰가 바뀌었다 → 애니메이션과 성능에 영향을 준다
- WWDC23 - Explore SwiftUI animations
-
ForEach의 데이터 개수는 일정해야 함 → 필터링이 필요하다면 모델로 정의
-
안 좋은 예시 → ForEach 내부에서 조건을 줌 → 모든 cell을 확인해야 함 → ForEach 내부에 뷰가 얼마나 정의되어 있는지 알 수 없기 때문
List { ForEach(dogs) { dog in if dog.fetchToy == .ball { DogCell(dog) } } }
-
안 좋은 예시2 → dogs 개수가 많아지면 업데이트가 느려질 수 있음
List { ForEach(dogs.filter(...)) { dog in DogCell(dog) } }
-
좋은 예시 → 모델로 정의 → 리스트 개수가 일정, 필터가 캐싱되서 업데이트 효율적임
List { ForEach(tennisBallDogs) { dog in DogCell(dog) } }
-
-
ForEach 내부에는 중첩된 구조 대신 평탄화 하는게 좋음
-
대신, 섹션화된 리스트는 중첩된 구조가 유용함
struct DogsByToy: View { var model: DogModel var body: some View { List { ForEach(model.dogToys) { toy in Section(toy.name) { ForEach(model.dogs(toy: toy)) { dog in DogCell(dog) } } } } } }
-
TableRow는 항상 단일 행임
- ForEach에서 TableRow를 사용하는 동작은 iOS 16 이하와 iOS 17 이상이 다름
- iOS 16 이하: TableRow로 주입된 값의 ID
- iOS 17 이상: 데이터 각각의 ID
-
dogs
의TableRow(dog.bestFriend)
라면, iOS 16 이하에선bestFriend.id
를, iOS 17부턴dogs[i].id
를 Table row ID로 사용함 - 테이블 행을 식별하기 위해 ForEach를 조사할 필요가 없어져서 성능 향상이 됨
- 버전마다 동작을 같게 하려면 명시적으로 ID를 지정해야 함
- ForEach에서 TableRow를 사용하는 동작은 iOS 16 이하와 iOS 17 이상이 다름
고병학 | 권승용 | 김대황 | 김인환 | 유정주 | 윤동주 | 이준복 | 이창준 | 정종인 | 홍승현 |
---|---|---|---|---|---|---|---|---|---|
bengHak | ericKwon95 | qwerty3345 | loinsir | jeongju9216 | yoondj98 | junbok97 | SwiftyJunnos | chongin12 | WhiteHyun |