🟣 뷰를 어떻게 갱신하지, 라는 질문이 사라진다
UIKit을 쓰다 SwiftUI로 넘어오면 가장 먼저 찾게 되는 것이 "화면을 다시 그리는 함수"다. UIKit에서는 그게 일이었기 때문이다. 카운트가 하나 늘면 countLabel.text를 새로 대입하고, 버튼을 잠가야 하면 button.isEnabled = false를 부른다. 데이터가 바뀔 때마다 그 변화를 화면에 반영하는 코드를 사람이 직접 써 준다. 이게 명령형 방식이다.
문제는 상태와 화면이 두 벌로 존재한다는 데 있다. 진짜 상태는 프로퍼티에 들어 있고, 화면은 그 상태를 베낀 사본이다. 둘을 매번 손으로 맞춰야 하는데, 상태를 바꾸는 지점이 늘어날수록 어딘가 한 곳에서 갱신을 빠뜨린다. 화면이 실제 데이터와 어긋나는 버그는 대개 여기서 나온다.
SwiftUI는 이 구조 자체를 없앤다. 화면을 어떻게 바꿀지 지시하는 대신, 지금 상태라면 화면이 어떤 모습이어야 하는지만 선언한다. 상태가 바뀌면 화면을 새로 맞추는 일은 프레임워크가 한다. 그래서 "뷰를 어떻게 갱신하지"라는 질문이 통째로 사라진다.
🟣 body는 상태로부터 화면을 만드는 함수다
SwiftUI 뷰의 핵심은 body다. body는 현재 상태를 받아 화면 구조를 돌려주는 계산이라고 보면 된다. 같은 상태면 같은 화면이 나온다. 카운터를 명령형과 선언형으로 나란히 써 보면 차이가 분명하다.
UIKit이라면 값이 바뀔 때마다 라벨을 직접 갱신해야 한다.
class CounterViewController: UIViewController {
var count = 0 {
didSet { countLabel.text = "\(count)" } // 바뀔 때마다 손으로 반영
}
@objc func tapPlus() {
count += 1
}
}SwiftUI에서는 라벨을 갱신하는 코드가 없다. count가 바뀌면 body가 다시 계산되고, 화면은 그 결과로 맞춰진다.
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack {
Text("\(count)") // count를 그대로 읽는다
Button("더하기") {
count += 1 // 값만 바꾼다. 화면 갱신은 지시하지 않는다
}
}
}
}여기서 @State는 "이 값은 뷰가 소유하고, 이 값이 바뀌면 화면을 다시 계산하라"는 표시다. count += 1 한 줄이 값을 바꾸는 동시에 화면 갱신을 예약하는 셈이다. 개발자는 무엇을 그릴지만 적고, 언제 어떻게 다시 그릴지는 SwiftUI가 정한다. 이 역할 분담이 선언형의 전부다.
body가 실제로 어떻게 다시 계산되고 화면에 반영되는지, 그 안에서 어떤 부분만 골라 갱신하는지는 렌더 루프와 diffing의 영역이다. 처음 배울 때는 그 아래층까지 파고들 필요가 없다. "값을 바꾸면 화면이 따라온다"는 감각만 확실히 잡으면 된다. 원리가 궁금해지면 애플 플랫폼 백과사전 쪽을 보면 된다.
🟣 뷰가 값 타입이라는 점
UIKit의 UIView는 클래스다. 한 번 만들면 계속 살아 있고, 우리는 그 객체를 참조로 붙잡아 속성을 바꿔 왔다. SwiftUI의 View는 struct, 즉 값 타입이다. 이 차이가 처음엔 불안하게 느껴진다. "매번 새로 만들면 성능이 괜찮나" 싶기 때문이다.
body가 만들어 내는 것은 무거운 화면 객체가 아니라 화면이 어떤 모습이어야 하는지 적은 가벼운 설명서다. SwiftUI는 이 설명서를 받아, 실제로 바뀐 부분만 골라 진짜 화면에 반영한다. 그래서 body가 자주 호출돼 struct가 여러 번 만들어지고 버려져도 부담이 크지 않다. 뷰를 가볍게 자주 만드는 것을 전제로 설계된 구조다.
이 사실이 실무에서 갖는 의미는 하나다. 뷰 안에 무거운 작업을 넣으면 안 된다. body는 자주 불릴 수 있으므로, 네트워크 요청이나 큰 계산을 body 계산 중에 하지 말고 상태를 바꾸는 이벤트 쪽에 두어야 한다.
🟣 정리
- 명령형 UIKit은 상태가 바뀔 때마다 화면을 손으로 갱신한다. 상태와 화면이 두 벌이라 어긋나기 쉽다.
- 선언형 SwiftUI는 "지금 상태면 화면이 어떤 모습인가"만 선언한다. 갱신은 프레임워크가 맡는다.
body는 상태로부터 화면 설명서를 만드는 함수다.@State값이 바뀌면body가 다시 계산된다.View는 값 타입이라 자주 만들어지고 버려진다. 무거운 작업을body에 넣으면 안 된다.- 내부 갱신 원리(렌더 루프, diffing)는 이 책의 범위가 아니다. 사용 감각을 먼저 잡는다.
면접에서 "UIKit과 SwiftUI의 차이"를 물으면 문법이 아니라 이 지점을 답하는 편이 낫다. 명령형은 화면 갱신을 개발자가 책임지고, 선언형은 상태를 진실의 원천으로 두고 화면을 파생시킨다는 것이다. 실무에서도 이 감각이 잡혀야 상태를 어디에 둘지 고민하기 시작한다. 그 고민이 다음 장의 주제인 데이터 흐름이다. 상태를 누가 소유하고 어떻게 아래로 흘려보내는지를 이어서 본다.