Skip to content

[정주] SwiftUI 구조체와 View 생성과 해제 테스트

유정주 JeongJu Yu edited this page Jul 20, 2024 · 1 revision

테스트 배경

  1. WWDC를 보며 구조체와 View의 생명주기 다름을 학습함
  2. 구조체는 Body를 생성한 후 메모리에서 즉시 해제된다고 이해함
  3. 만약 구조체가 즉시 해제된다면 Body의 변화는 어떻게 감지하는지 궁금증이 생김
  4. 스터디원과 여러 토의를 거친 결과, WWDC의 문장 그대로 "즉시" 해제되는 것은 아니라고 판단함
  5. 구조체의 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

출력 결과 해석

  1. Contentbody가 업데이트됨
  2. Ainit이 호출됨
  3. Ainit, body가 호출된 뒤 이전 dummy class가 deinit

2. A Button 클릭 (두 번)

출력 결과

Click A Button
A Body: 25A2EC
Click A Button
A Body: 25A2EC

출력 결과 해석

  1. A의 Button 액션은 미리 등록되었기 때문에 A의 변경만 보면 됨 -> Contentbody는 호출되지 않음
  2. A의 SOT가 변경되었으므로 Abody만 호출됨
  3. Ainit이 호출되지 않았으므로 dummy class의 id가 동일함

3. ❓ Content Button 클릭 ❓

출력 결과

Click Content Button
Content Body
DummyClass init: 582BA7
A Init: 582BA7
A Body: 582BA7

출력 결과 해석

  1. 기존 A의 dummy class deinit이 호출되지 않음
  2. 새로운 Ainit, body 호출됨
  3. 기존 A의 구조체는 살아있음
  • 메모리에 A 구조체가 두 개 잡혀있음

4. ⭐️ 3번 시나리오 후 A Button 클릭 ⭐️

출력 결과

DummyClass deinit: 25A2EC
Click A Button
A Body: 582BA7

출력 결과 해석

  1. 기존 A의 dummy class가 deinit
  2. 그다음 A Button의 액션이 호출됨
  3. Abody가 호출됨

종합 해석

  1. SwiftUI의 View 구조체는 Body를 그리는 즉시 해제되지 않음 (3번 테스트)
  2. Body를 새로 그리기 전까지 기존 View의 구조체를 유지함. (4번 테스트)

의문인 점

  • 3번 테스트에서 기존 A 구조체가 해제되지 않은 채 새로운 A 구조체가 생성된 이유는 무엇일까
    • 가설1) View마다 구조체를 따로 관리한다. (e.g. ContentViewAView가 별개의 AView 구조체를 관리한다.)
      • Content Button을 눌렀을 때 25A2EC가 최초 생성됨 -> ContentViewAView 구조체(25A2EC)가 추가된다.
      • A Button을 눌렀을 때 AView의 구조체 관리에 25A2EC가 추가된다.
      • Content Button을 눌렀을 때 새로운 A 구조체를 생성, 25A2EC 관리는 제거된다.
      • A Button을 눌렀을 때 AView가 관리하는 AView 구조체가 25A2EC에서 582BA7로 변경됨. 따라서 25A2EC를 관리하는 View가 하나도 없으므로 25A2ECdeinit
Clone this wiki locally