Written on
·
57
1
ObservableObject를 활용하는 것보다,
새로운 Observable Macro를 활용하여 Migrating을 시도하려 했습니다.
주어진 WeakSelfInterViewModel
에 대해 @Observable을 적용하고, var data String? = nil
로 property를 만들었습니다. 하지만 Init과 Deinit이 반복되어 나타나는 무한루핑이 발생하였습니다.
class WeakSelfInterViewModel{
var data: String? = nil
... }
struct WeakSelfInter2: View {
@State var vm: WeakSelfInterViewModel = WeakSelfInterViewModel()
... }
어떻게 해결할 수 있을까요?
Answer 2
1
안녕하세요 ycc3819 님.
저도 확인한 결과 @Observable 로 변경할 시 무한 루핑이 발생되는것을 확인하였습니다.
발생한 원인을 보자면..
1. 무한 루핑 발생 원인
• @State는 값 타입 상태를 관리하기 위해 설계되었습니다. 하지만 @Observable은 클래스 타입(참조 타입)을 기반으로 동작하며, SwiftUI가 상태 변경을 감지할 때 ViewModel이 반복적으로 초기화되는 문제가 발생합니다.
• 이로 인해 init과 deinit이 무한히 호출되며 루핑이 발생합니다.
2. 그래서 강의 코드 처럼 @StateObject를 사용해야 하는 이유
• 기존 강의 코드에서 @StateObject는 ObservableObject와 함께 사용되며, SwiftUI 뷰의 생명주기와 ViewModel의 초기화/해제를 안정적으로 관리합니다.
• @StateObject는 뷰가 다시 렌더링되더라도 ViewModel을 재사용하기 때문에 무한 루핑 문제가 발생하지 않습니다.
그래서 강의 내용 코드를 @Observable 로 바꿀때는 강의에서 다루지 않은 .onAppear, .onDisappear 를 사용해서 Weak self 약한 참조 내용을 확인 하실 수 있습니다. (@State, 와 init, deinit 을 사용하게 되면 라이프 사이클 충돌로 인해 무한 루핑이 되기 때문입니다. )
예시 코드
import SwiftUI
import Observation // @Observable 사용을 위해 임포트
// MARK: - VIEWMODEL
@Observable
class WeakSelfInterViewModel {
var data: String? = nil // 데이터를 관리하는 상태 변수
init() {
getData() // 초기 데이터 로드
}
func getData() {
// 비동기로 데이터를 로드하며, 강한 참조 순환을 방지하기 위해 [weak self] 사용
DispatchQueue.main.asyncAfter(deadline: .now() + 10) { [weak self] in
self?.data = "NEW DATA!" // 데이터 업데이트
}
}
}
// MARK: - SCREEN 1
struct WeakSelfInter: View {
var body: some View {
NavigationStack {
// 2번 페이지로 이동하며 ViewModel은 2번 페이지에서만 생성 및 관리
NavigationLink("2번째 페이지로 이동") {
WeakSelfInter2()
}
.navigationTitle("1번째 페이지") // 네비게이션 타이틀 설정
}
}
}
// MARK: - SCREEN 2
struct WeakSelfInter2: View {
@State private var vm = WeakSelfInterViewModel() // @State로 ViewModel 관리
var body: some View {
VStack {
Text("2번째 페이지")
.font(.largeTitle)
.foregroundColor(.red)
// ViewModel의 데이터가 있으면 표시
if let data = vm.data {
Text(data)
}
}
.onAppear {
// 뷰가 나타날 때 데이터 로드 확인
print("뷰가 나타남")
}
.onDisappear {
// 뷰가 사라질 때 로그 출력
print("뷰가 사라짐")
// ViewModel은 @State로 관리되므로 자동으로 메모리에서 해제됨
}
}
}
// MARK: - PREVIEW
#Preview {
WeakSelfInter() // 미리보기에서 WeakSelfInter 화면 확인
}
0
안녕하세요, 인프런 AI 인턴입니다. ycc3819님.
init-deinit
의 무한 루핑 문제는 메모리 관리 및 상태 변경 흐름에서 발생할 수 있는 일반적인 문제 중 하나입니다. 주로 View와 ViewModel 간의 상태 변화가 반복적으로 일어나면서 발생하게 됩니다.
이 문제를 해결하기 위한 몇 가지 접근 방법을 제시해드리겠습니다.
ObservableObject 및 특성 래퍼 사용:
ObservableObject
와 @Published
를 사용할 때, 적절한 상태 관리를 해야 합니다. @State
및 @StateObject
를 정확히 사용하여 상태가 변경될 때마다 변경 사항을 반영하도록 해야 합니다.Reference Cycle 확인:
self
를 참조하고 있는 경우, 이를 해결하기 위해 weak
또는 unowned
를 사용해 보세요. 이는 메모리 누수를 방지하고, 맞물린 리소스들이 올바르게 해제되도록 도와줍니다.SwiftUI 라이프사이클 준수:
@StateObject
를 사용하여 뷰 밖에서 상태를 관리하는 것을 추천드립니다.다른 관련된 질문에 대한 게시물을 참조해 보세요. 특히 SwiftUI에서 states와 lifecycle을 적절하게 다루는 방법에 대한 내용은 많은 개발자들이 공유하고 있는 주제입니다.
유사한 사례를 다룬 두 가지 관련된 기존 Q&A를 추천드립니다:
- 강의 Weak Self 코드 실행시 오류에 대해: 이 게시물에서 강사님의 코드 리뷰를 통해 문제 해결 방법을 제시하고 있습니다.
- 안녕하새요(ViewModel): ViewModel 및 상태 관리에 관한 기초 개념을 이해하는 데 유용한 글입니다.
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.
단순히 @StateObject를 사용할때 ViewModel을 객체화 하여 View가 처음 생성, 초기화 될 때 활용한다는 사실만 알고 있었는데, View가 다시 렌더링 되더라도, 기존 ViewModel을 재사용하기에 가능하다는 말씀이신거군요!
새로 하나 배워갑니다. 감사합니다!