티스토리 뷰

 

프로젝트에서 Timer를 사용하는데 Timer가 중복으로 실행되어 실행횟수만큼 초가 플러스 되는 문제가 생겼다.

그 문제는 @State의 잘못된 사용 때문이었다,, 간단한 예시를 들어 설명해보겠다.

 

 

✏️ @State란??

 

 

@State는 SwiftUI에서 사용되는 속성 래퍼로, 값의 변경을 감지고 뷰를 자동으로 업데이트하는 데 사용된다.

@State 속성은 주로 View 구조체 내에서 사용된다고 한다. 왜 그런지는 아래에서 설명하겠다.

 

 

 

 

1️⃣ View의 내부에서 @State 사용

 

 

View 내부에서 @State를 사용하여 currentTimer, isTimerRunning 변수, timer를 선언해보자.

 

https://developer.apple.com/documentation/swiftui/state

 

@State를 사용할 때 주의할 점으로는 항상 private로 선언하고 가장 상위 뷰에서 @State를 관리해야한다고 나와있다.

- 뷰를 초기화할 때, @State 속성 값도 같이 초기화하게 되면 SwiftUI에서 @State 속성을 관리하는 공간인 Storage에서 conflict가 나기 때문이라고 한다. 

 

예제에서는 ContentView 하나만 존재하니 private만 신경써서 선언하였다.

 

import SwiftUI

struct ContentView: View {
    @State private var currentTime = 0
    @State private var isTimerRunning = false // 타이머 실행 여부를 저장하는 상태 변수
    @State private var timer: Timer? // 타이머 인스턴스를 저장하는 상태 변수

    var body: some View {
        VStack {
            Text("\(currentTime)")

            Button(action: {
                if self.isTimerRunning {
                    self.stopTimer()
                } else {
                    self.startTimer()
                }
            }) {
                Text(self.isTimerRunning ? "타이머 멈추기" : "타이머 시작하기")
            }
            .padding()
        }
    }

    // 타이머 시작 함수
    private func startTimer() {
        self.isTimerRunning = true
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
            self.currentTime += 1
        }
    }

    // 타이머 멈추기 함수
    private func stopTimer() {
        self.isTimerRunning = false
        timer?.invalidate()
        timer = nil
        currentTime=0
    }
}

 

위의 코드인 경우 @State 속성으로 선언한 변수들이 변경되면 뷰를 자동으로 업데이트한다. 

 

  • timer 시작하는 startTimer()가 실행되면 @State 속성으로 선언한 currentTime의 값이 변경되어 뷰를 자동으로 업데이트하고 변경된 값을 화면에 보여주는 것이다.
  • timer 멈추고 초기화하는 stopTimer()가 실행되면 @State 속성으로 선언한 currentTime과 isTimerRunning의 값이 변경되어 타이머를 해제하고, 시간을 초기화한다. 

 

 

 

 

실행해보면 문제없이 잘 실행되는 것을 확인할 수 있다.

 

 

2️⃣ ViewModel에서 @State 사용

 

 

timer 동작 로직을 ViewModel로 빼서 구현하였고 똑같이 @State 속성으로 timer를 선언하였다.

1️⃣과 달라진 점이라고는 ViewModel로 뺀 것 밖에 없다.

 

하지만 결과는 아래와 같이 타이머가 1초씩 증가하는게 아니라 실행 횟수만큼 증가되는 문제가 있었다.

 

 

왜 이런 문제가 발생한 걸까,,,,,

 

@State는 SwiftUI 뷰 내부 상태를 나타내는 데 사용되는데, ViewModel은 뷰의 외부에 존재하는 독립적인 객체이기 때문이다.

뷰의 내부 상태가 아니기 때문에 timer는 @State 속성으로 선언되면 안된다고 한다 ㅎㅎ

 

그래서 timer의 @State 속성 때문에 startTimer()가 실행될 때마다 뷰가 업데이트되어 timer가 새로 만들어졌던 것이었고, 함수 실행횟수만큼 타이머의 초 카운트도 증가하게 되었다.

 

  • ViewModel
import SwiftUI

class TimerViewModel: ObservableObject {
    @Published var currentTime = 0
    @Published var isTimerRunning = false
    @State private var timer: Timer?//@State 사용 x

    func startTimer() {
        isTimerRunning = true
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
            self.currentTime += 1
        }
    }

    func stopTimer() {
        isTimerRunning = false
        timer?.invalidate()
        timer = nil
        currentTime = 0
    }
}

 

  • View
struct ContentView: View {
    @ObservedObject var viewModel = TimerViewModel()

    var body: some View {
        VStack {
            Text("\(viewModel.currentTime)")

            Button(action: {
                if self.viewModel.isTimerRunning {
                    self.viewModel.stopTimer()
                } else {
                    self.viewModel.startTimer()
                }
            }) {
                Text(self.viewModel.isTimerRunning ? "타이머 멈추기" : "타이머 시작하기")
            }
            .padding()
        }
    }
}

 

 

@State private var timer: Timer?private var timer: Timer?로 바꿔주니 문제가 해결되었다.

 

이렇게 하면 뷰의 업데이트와는 독립적으로 타이머를 관리할 수 있고, 타이머를 다룰 때 중복 실행을 방지하고 이전 타이머를 제대로 해제할 수 있다.

 

'스위프트 > SwiftUI' 카테고리의 다른 글

[SwiftUI] - Unit Testing  (0) 2024.05.13
[SwiftUI] - Kakao ID 토큰에 nonce 추가  (0) 2024.04.25
[SwiftUI] - NavigationLink Action 추가  (0) 2024.04.07
[SwiftUI] - Custom Font 적용  (0) 2024.03.19
[SwiftUI] - ObservableObject 예제  (0) 2024.03.12
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함