[정주] SwiftUI 구조체와 View 생성과 해제 테스트 - BoostSwiftUI/SwiftUI GitHub Wiki
테스트 배경
- WWDC를 보며 구조체와 View의 생명주기 다름을 학습함
- 구조체는 Body를 생성한 후 메모리에서 즉시 해제된다고 이해함
- 만약 구조체가 즉시 해제된다면 Body의 변화는 어떻게 감지하는지 궁금증이 생김
- 스터디원과 여러 토의를 거친 결과, WWDC의 문장 그대로 "즉시" 해제되는 것은 아니라고 판단함
- 구조체의
deinit
은 존재하지 않으므로, Class 프로퍼티를 이용한 편법을 통해 구조체 해제 시점을 확인해 봄
테스트 코드
ContentView
, AView
의 각 count 프로퍼티는 SOT 변경을 통한 Body 업데이트 목적으로 정의함
DummyClass
를 일반 프로퍼티로 정의한 이유
- 구조체가 해제될 때 함께
deinit
시키기 위해 일반 프로퍼티로 정의함
- SOT로 정의한다면 구조체가 해제되도 데이터가 유지되서 원하는 시나리오 테스트가 불가
- 해당 테스트 코드는 시뮬레이터에서 실행함
import SwiftUI
final class DummyClass {
let id = String(Array(UUID().uuidString)[..<6])
init() {
print("DummyClass init: \(id)")
}
deinit {
print("DummyClass deinit: \(id)")
}
}
struct ContentView: View {
@State var count = 0
init() {
print("Content Init")
}
var body: some View {
let _ = print("Content Body")
AView()
Button(
action: {
let _ = print("Click Content Button")
count += 1
}, label: {
Text("Content Button: \(count)")
}
)
}
}
struct AView: View {
@State var count = 0
private let dummyClass = DummyClass()
init() {
print("A Init: \(dummyClass.id)")
}
var body: some View {
let _ = print("A Body: \(dummyClass.id)")
Button(
action: {
let _ = print("Click A Button")
count += 1
}, label: {
Text("A Button: \(count)")
}
)
}
}
테스트 시나리오
1. 앱 실행 -> Content Button
클릭
출력 결과
Content Init
Content Body
DummyClass init: CEB848
A Init: CEB848
A Body: CEB848
Click Content Button
Content Body
DummyClass init: 25A2EC
A Init: 25A2EC
A Body: 25A2EC
DummyClass deinit: CEB848
출력 결과 해석
Content
의 body
가 업데이트됨
A
의 init
이 호출됨
A
의 init
, body
가 호출된 뒤 이전 dummy class가 deinit
됨
2. A Button 클릭 (두 번)
출력 결과
Click A Button
A Body: 25A2EC
Click A Button
A Body: 25A2EC
출력 결과 해석
A
의 Button 액션은 미리 등록되었기 때문에 A
의 변경만 보면 됨 -> Content
의 body
는 호출되지 않음
A
의 SOT가 변경되었으므로 A
의 body
만 호출됨
A
의 init
이 호출되지 않았으므로 dummy class의 id가 동일함
3. ❓ Content Button 클릭 ❓
출력 결과
Click Content Button
Content Body
DummyClass init: 582BA7
A Init: 582BA7
A Body: 582BA7
출력 결과 해석
- 기존
A
의 dummy class deinit
이 호출되지 않음
- 새로운
A
의 init
, body
호출됨
- 기존
A
의 구조체는 살아있음
4. ⭐️ 3번 시나리오 후 A Button 클릭 ⭐️
출력 결과
DummyClass deinit: 25A2EC
Click A Button
A Body: 582BA7
출력 결과 해석
- 기존
A
의 dummy class가 deinit
됨
- 그다음
A
Button의 액션이 호출됨
A
의 body
가 호출됨
종합 해석
- SwiftUI의 View 구조체는 Body를 그리는 즉시 해제되지 않음 (3번 테스트)
- Body를 새로 그리기 전까지 기존 View의 구조체를 유지함. (4번 테스트)
의문인 점
- 3번 테스트에서 기존
A
구조체가 해제되지 않은 채 새로운 A
구조체가 생성된 이유는 무엇일까
- 가설1)
View
마다 구조체를 따로 관리한다. (e.g. ContentView
와 AView
가 별개의 AView
구조체를 관리한다.)
Content
Button을 눌렀을 때 25A2EC
가 최초 생성됨 -> ContentView
에 AView
구조체(25A2EC)가 추가된다.
A
Button을 눌렀을 때 AView
의 구조체 관리에 25A2EC
가 추가된다.
Content
Button을 눌렀을 때 새로운 A
구조체를 생성, 25A2EC
관리는 제거된다.
A
Button을 눌렀을 때 AView
가 관리하는 AView
구조체가 25A2EC
에서 582BA7
로 변경됨. 따라서 25A2EC
를 관리하는 View가 하나도 없으므로 25A2EC
는 deinit
됨