setNeedsDisplay(): 메서드의 본질과 iOS에서의 다시 그리기 메커니즘

저자: IT Sectr 게시일: 2026-07-20 읽는 시간: 6 분

setNeedsDisplay()는 UIView의 인스턴스 메서드로, 뷰를 다시 그려야 하는 것으로 표시합니다. draw(_:)를 직접 호출하는 것과 달리 setNeedsDisplay()는 뷰의 dirty 플래그만 설정하고 즉시 제어를 반환합니다. 시스템은 가장 가까운 렌더링 사이클에서 이 요청을 처리하여 여러 호출을 단일 그리기 작업으로 결합할 수 있습니다. Apple 문서(2025)에 따르면, setNeedsDisplay()를 통한 지연된 다시 그리기는 사용자 정의 UIView의 콘텐츠를 업데이트하는 유일한 올바른 방법입니다.

핵심 사항

  • setNeedsDisplay() — UIView에 대한 지연된 다시 그리기 요청, draw(_:)를 동기적으로 호출하지 않음
  • dirty 플래그 — setNeedsDisplay()에 의해 설정되고 draw(_:) 후에 지워지는 내부 뷰 플래그
  • setNeedsDisplay(_:) — 지정된 영역만 부분적으로 다시 그리기 위한 CGRect 오버로드
  • displayIfNeeded() — 강제 동기 다시 그리기, 즉시 업데이트에 사용
  • 렌더링 사이클 — 시스템이 run loop 끝에서 dirty 뷰를 처리하고 여러 요청을 하나의 패스로 결합

setNeedsDisplay()란 무엇인가

setNeedsDisplay()는 UIView 메서드로, 뷰의 콘텐츠가 오래되어 다시 렌더링해야 함을 시스템에 알립니다. 이 메서드는 매개변수를 받지 않으며 void를 반환합니다. 호출 후 뷰는 dirty로 표시되고, 가장 가까운 그리기 사이클에서 시스템이 이 뷰에 대해 draw(_:)를 호출합니다.

핵심 특징은 비동기성입니다. 동일한 뷰에 대해 연속적으로 여러 setNeedsDisplay()를 호출해도 여러 draw(_:) 호출이 발생하지 않습니다 — 시스템이 이를 하나의 작업으로 결합합니다. 이는 성능에 매우 중요합니다: 뷰 속성이 프레임당 여러 번 변경되어도 draw(_:)는 한 번만 호출됩니다.

지연된 다시 그리기 작동 방식

지연된 다시 그리기 메커니즘은 UIKit 렌더링 사이클을 기반으로 합니다. setNeedsDisplay()가 호출되면 시스템은 내부 플래그 layer.needsDisplay를 true로 설정합니다. 현재 run loop가 끝나면 시스템은 모든 dirty 레이어를 순회하고 각각에 대해 draw(_:)를 호출합니다.

Run loop 및 렌더링 단계

UIKit run loop에는 dirty 뷰가 처리되는 업데이트 단계가 포함됩니다. 이 단계에서는 필요 시 layoutSubviews()가 호출되고, 그 다음 표시된 모든 뷰에 대해 draw(_:)가 호출됩니다. 그리기가 완료되면 결과는 컴포지팅을 위해 Core Animation을 통해 GPU로 전달됩니다.

swift
class ProgressView: UIView {
    var progress: CGFloat = 0 {
        didSet {
            setNeedsDisplay()
        }
    }

    override func draw(_ rect: CGRect) {
        super.draw(rect)
        guard let ctx = UIGraphicsGetCurrentContext() else { return }

        // 배경
        ctx.setFillColor(UIColor.lightGray.cgColor)
        ctx.fill(rect)

        // 진행 바
        let fillRect = CGRect(x: rect.minX, y: rect.minY,
                              width: rect.width * progress, height: rect.height)
        ctx.setFillColor(UIColor.systemGreen.cgColor)
        ctx.fill(fillRect)
    }
}

이 예제에서 progressView는 progress를 업데이트하고 각 setter는 자동으로 setNeedsDisplay()를 호출합니다. 시스템은 run loop가 끝날 때까지 기다렸다가 실제 progress 값으로 draw(_:)를 호출합니다. progress가 프레임당 세 번 변경되어도 draw(_:)는 마지막 값으로 한 번만 실행됩니다.

setNeedsDisplay() vs setNeedsDisplay(_:)

setNeedsDisplay(_:) 메서드는 CGRect 유형의 rect 매개변수를 받아 다시 그리기 영역을 제한합니다. 이를 통해 뷰 전체가 아닌 변경된 부분만 다시 그릴 수 있습니다. 성능 차이는 변경 사항이 작은 영역에 영향을 미치는 큰 뷰로 작업할 때 두드러집니다.

특성setNeedsDisplay()setNeedsDisplay(_:)
매개변수없음CGRect — 다시 그리기 영역
다시 그리기 영역전체 뷰 bounds지정된 사각형만
성능큰 뷰에서 낮음부분 변경에서 높음
draw(_:)의 rect 매개변수전체 bounds지정된 사각형
사용 시기전체 콘텐츠 변경부분 변경 (커서, 하이라이트)

복잡한 그래픽의 경우 먼저 오프스크린 컨텍스트에 전체 장면을 그린 다음 변경된 영역에 대해서만 setNeedsDisplay(_:)를 호출하는 것이 좋습니다. 이는 iOS의 그리기 애플리케이션 및 그래픽 편집기에서 표준 기술입니다.

displayIfNeeded() 대 setNeedsDisplay()

displayIfNeeded()는 동기 메서드로, run loop가 끝날 때까지 기다리지 않고 계층 구조의 모든 dirty 뷰 다시 그리기를 즉시 시작합니다. 플래그만 설정하는 setNeedsDisplay()와 달리 displayIfNeeded()는 draw(_:)의 즉시 실행을 강제합니다.

Apple은 그리기 결과가 즉시 필요한 극단적인 경우에만 displayIfNeeded()를 사용할 것을 권장합니다 — 예를 들어, 스크린샷을 찍기 전이나 UIGraphicsImageRenderer의 동기 이미지 생성 시. 일반적인 시나리오에서는 메인 스레드를 차단하지 않는 setNeedsDisplay()를 통한 비동기 모델이 선호됩니다.

swift
    // 비동기 — 권장
someView.setNeedsDisplay()

    // 동기 — 필요할 때만
someView.setNeedsDisplay()
someView.displayIfNeeded() // draw(_:) 즉시 호출됨

선행 setNeedsDisplay() 없이 displayIfNeeded()를 호출해도 효과가 없습니다 — 이 메서드는 이미 dirty로 표시된 뷰만 처리합니다. 다시 그리기가 필요한 뷰가 없으면 displayIfNeeded()는 draw(_:)를 호출하지 않고 완료됩니다.

setNeedsDisplay() 호출 최적화

빈번한 setNeedsDisplay() 호출은 특히 애니메이션 중에 성능을 저하시킬 수 있습니다. 주요 최적화 전략을 살펴보겠습니다.

모델을 통한 변경 사항 배치 처리

개별 속성이 변경될 때마다 setNeedsDisplay()를 호출하는 대신 변경 사항을 누적하고 모든 업데이트를 적용한 후 메서드를 한 번 호출합니다. 예를 들어, 요소의 색상, 크기 및 위치를 변경할 때 — 모든 속성을 설정한 후 setNeedsDisplay()를 한 번 호출합니다.

애니메이션 가능 속성에 CALayer 사용

속성이 CALayer(backgroundColor, opacity, position, transform)를 통해 애니메이션될 수 있는 경우 draw(_:) 대신 Core Animation을 사용하세요. CALayer 속성은 draw(_:)를 호출하지 않고 GPU에서 업데이트되어 CPU 부하 없이 60 FPS를 제공합니다.

CATransaction을 통한 배치 업데이트

여러 변경 사항을 단일 렌더링 패스로 그룹화하려면 CATransaction을 사용하세요. 이를 통해 트랜잭션 끝에서 여러 setNeedsDisplay() 호출을 하나의 draw(_:) 작업으로 결합할 수 있습니다.

swift
CATransaction.begin()
CATransaction.setDisableActions(true)

view1.setNeedsDisplay()
view2.setNeedsDisplay()
view3.setNeedsDisplay()

CATransaction.commit()

displayIfNeeded()를 남용하지 마세요 — 동기 다시 그리기는 메인 스레드를 차단하고 프레임 드롭을 유발할 수 있습니다. 현재 run loop가 끝나기 전에 그리기 결과가 중요한 작업에만 사용하세요.

자주 묻는 질문

setNeedsDisplay() 대신 직접 draw(_:)를 호출할 수 있나요?

아니요, draw(_:)를 직접 호출하는 것은 금지됩니다. setNeedsDisplay()는 뷰를 dirty로 올바르게 표시하고 시스템이 렌더링 사이클의 적절한 시점에 draw(_:)를 호출합니다. draw(_:)를 직접 호출하면 캐싱 메커니즘이 무시되고 일관성 없는 상태가 발생할 수 있습니다.

setNeedsDisplay()를 여러 번 호출하면 어떻게 되나요?

동일한 뷰에 대한 여러 setNeedsDisplay() 호출은 시스템에 의해 단일 draw(_:) 호출로 결합됩니다. 이는 메서드가 dirty 플래그만 설정하고 run loop 끝에서 draw(_:)가 실제로 실행된 후에만 지워지기 때문입니다.

setNeedsDisplay()와 setNeedsLayout()의 차이점은 무엇인가요?

setNeedsDisplay()는 뷰 콘텐츠의 다시 그리기를 요청합니다 (draw(_:) 호출). setNeedsLayout()은 서브뷰의 재배치를 요청합니다 (layoutSubviews() 호출). layoutSubviews()는 크기 변경을 초래할 수 있으며, 이는 다시 그리기를 트리거할 수 있습니다.

setNeedsDisplay(_:)는 언제 사용해야 하나요?

setNeedsDisplay(_:)는 부분 변경에 사용해야 합니다: 커서 이동, 선택 영역 색상 변경, 그래프 일부 업데이트. 이는 다시 그리기 영역을 제한하고 전체 뷰를 다시 그리는 것보다 성능을 향상시킵니다.

frame 변경이 자동으로 setNeedsDisplay()를 호출하나요?

기본적으로 frame 또는 bounds 변경은 setNeedsDisplay()를 호출하지 않습니다. 시스템은 단순히 뷰를 이동합니다. 크기 변경 시 다시 그리기가 필요한 경우 contentMode 속성을 .redraw로 설정하세요 — 그러면 UIKit이 bounds가 변경될 때마다 자동으로 setNeedsDisplay()를 호출합니다.

요약

  • setNeedsDisplay() — dirty 플래그를 설정하여 지연된 다시 그리기를 요청하는 UIView의 비동기 메서드
  • setNeedsDisplay(_:) — 지정된 영역만 부분적으로 다시 그리기 위한 CGRect 오버로드, 성능 최적화
  • displayIfNeeded() — dirty 뷰의 즉시 다시 그리기를 위한 동기 메서드, 절대적으로 필요한 경우에만 사용
  • Run loop — 시스템이 현재 사이클 끝에서 dirty 뷰를 처리하고 여러 호출을 하나의 draw(_:) 패스로 결합
  • CALayer — 애니메이션 가능 속성(position, opacity)에는 draw(_:) 대신 Core Animation 사용 — 훨씬 더 효율적
  • CATransaction — 여러 변경 사항을 하나의 렌더링 사이클로 그룹화하여 draw(_:) 호출 횟수 감소
  • contentMode = .redraw — 뷰의 bounds 변경 시 자동 setNeedsDisplay() 호출 활성화

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기