반응형

SwiftUI에서 많이 하는 실수 - onChange(of:)를 잘못 사용해 과도한 리렌더링이 일어나는 실수

 

onChange(of:)는 특정 값이 변할 때마다 콜백을 실행하는 유용한 도구입니다.
하지만 매 타이핑마다, 스크롤마다, 작은 변화마다 무거운 작업을 수행하면
성능 문제가 바로 드러납니다.


1. 문제 원인

  • 텍스트 필드 입력 값이 바뀔 때마다 네트워크 검색 API 호출
  • onChange 안에서 곧바로 정렬/필터/디스크 쓰기 등의 무거운 작업 수행
  • debounce/throttle 개념 없이 모든 변화를 1:1로 처리

2. 나타나는 증상

  • 검색창에 글자를 빨리 입력하면 앱이 버벅거림
  • 서버에 불필요하게 많은 검색 요청이 날아감
  • onChange 내부에서 상태를 또 바꾸다가 무한 루프/복잡한 흐름 발생

3. 잘못된 코드 예시

struct SearchView: View {
    @State private var query: String = ""
    @StateObject private var viewModel = SearchViewModel()

    var body: some View {
        VStack {
            TextField("검색", text: $query)
                .textFieldStyle(.roundedBorder)
                .onChange(of: query) { newValue in
                    // ❌ 타이핑마다 API 호출
                    Task {
                        await viewModel.search(keyword: newValue)
                    }
                }

            List(viewModel.results, id: \.id) { result in
                Text(result.title)
            }
        }
    }
}

4. 올바른 코드 예시 (debounce 적용)

4-1. 간단한 debounce 구현

@MainActor
final class SearchViewModel: ObservableObject {
    @Published var results: [SearchResult] = []

    private var searchTask: Task<Void, Never>?

    func searchDebounced(keyword: String) {
        searchTask?.cancel()
        searchTask = Task {
            try? await Task.sleep(nanoseconds: 300_000_000) // 0.3초
            guard !Task.isCancelled else { return }
            await search(keyword: keyword)
        }
    }

    func search(keyword: String) async {
        // 실제 검색 API 호출
    }
}

4-2. View에서 사용

struct SearchView: View {
    @State private var query: String = ""
    @StateObject private var viewModel = SearchViewModel()

    var body: some View {
        VStack {
            TextField("검색", text: $query)
                .textFieldStyle(.roundedBorder)
                .onChange(of: query) { newValue in
                    viewModel.searchDebounced(keyword: newValue)
                }

            List(viewModel.results, id: \.id) { result in
                Text(result.title)
            }
        }
    }
}

5. 정리 및 팁

  • onChange는 “변화가 자주 일어나는 값”에 연결할수록,
    내부에서 수행하는 작업을 더 가볍게 유지해야 합니다.
  • 네트워크 호출/디스크 쓰기/무거운 필터링 등이 포함된다면
    debounce/throttle 같은 완충 장치를 두는 것을 기본으로 생각해야 합니다.
반응형
Posted by 까칠코더
,