view가 로드되거나 변경이 있을때, 화면에 그려지는 cycle
View에는 시각적으로 표현되는 컨텐츠들이 많다.
만약 변화가 있을 때마다 매번 View의 모든 컨텐츠를 다시 그리는 것은 비효율적일 것이다.
따라서 iOS의 View는 업데이트할 컨텐츠가 생기면 즉각 그리지 않는다.
대신 다음 drawing cycle을 기다렸다가 한번에 그 내용을 그린다.
UIView클래스는 컨텐츠를 표시할 때, on-demand 드로잉 모델을 사용!
drawing cycle의 대략적인 순서는
1️⃣ View가 화면에 로드되면, 시스템이 UIView에게 draw 메서드
를 통해 드로잉을 요청
2️⃣ 시스템은 이 컨텐츠의 스냅샷을 캡쳐하고, 해당 스냅샷을 View의 시각적 표현(visual representation)으로 사용
3️⃣ View의 컨텐츠 변경 시 관련 메서드 (SetNeedsDisplay, SetNeedsLayout, displayIfNeeded, layoutIfNeeded) 호출하여 시스템에 업데이트 요청
4️⃣ Next Drawing Cycle에서 업데이트 요청 받은 뷰를 업데이트
-> View의 스냅샷을 캡쳐하고 뿌려주는 프로세스를 반복하는 과정!
참고
1️⃣ app이 실행되면 iOS의 UIApplication이 Main Thred에서 main run loop를 실행
2️⃣ main run loop는 loop를 돌며 이벤트를 처리
3️⃣ 이 처리 과정은 각 이벤트들에 맞는 핸들러를 찾고,
4️⃣ 해당 핸들러에게 처리 권한을 위임함
5️⃣ 발생한 이벤트들을 모두 처리(핸들러가 권한을 위임받고 처리)
6️⃣ 다시 main run loop가 이벤트 처리 권한을 가져오고, 이 시점에서 update cycle이 시작
만약 main run loop에서
view의 layout이나 position이 바뀌는 이벤트가 발생했다면?
layout이나 position 값을 바꾸는 핸들러가 실행!
이러한 변화는 즉각적으로 반영되지 않음
1️⃣ 시스템은 layout이나 position이 변경되는 View를 체크
2️⃣ 해당 이벤트를 처리할 수 있는 핸들러가 권한을 위임 받음
3️⃣ 핸들러가 종료되고 다시 main run loop로 권한이 돌아옮
4️⃣ update cycle이 시작
5️⃣ update cycle 시점에서 해당 View의 값을 변경
이렇게 layout이나 position 값을 변경하는 코드와 화면에 반영되는 시점이 명확히 다르다.
이런 시점차이를 인지하고 있어야 원하는 핸들러를 구현할 수 있다.
View 컨텐츠 변경 관련한 업데이트 트리거 종류
1️⃣ View를 부분적으로 가린 타 View의 이동 및 제거
2️⃣ 히든 뷰 노출
3️⃣ 뷰를 화면 밖과 안으로 이동 및 스크롤
4️⃣ UIView의 SetNeedsDisplay()메서드 또는 setNeedsDisplayInRect(:) 메소드 명시적으로 호출
📌 공식문서에서 트리거의 종류를 이렇게 4가지로 설명하고 있으나 이것은...레거시 문서
실제로는 4️⃣에 해당하는 SetNeedsDisplay()메서드 호출을 트리거로 사용
SetNeedsDisplay()메서드를 호출하게 되면
다음 Drawing Cycle이 오면 draw메소드를 호출해서 View를 업데이트
위에서 언급한 것처럼 결국 draw 메서드를 이용해서 View에 컨텐츠를 그린다.
하지만 darw를 직접 호출하지않고 setNeedsDisplay() 호출하여 자동으로 darw메서드가 불리도록 해야한다.
1️⃣ setNeedsDisplay()를 사용 -> View의 내용을 다시 그려야한다고 시스템에게 알림
2️⃣ setNeedsDisplay()는 요철을 기록하고 즉시 리턴
3️⃣ 하지만 View는 아직 그려지지 않고 다음 drawing cycle까지 기다린다.
4️⃣ 다음 drawing cycle 시점에서 draw 메서드가 호출되어 무효화된 View가 업데이트된다.
layout을 drawing 하는 UIView의 인스턴스 메서드는 SetNeedsLayout, displayIfNeeded, layoutIfNeeded
UIView Layout Method인 SetNeedsLayout와 layoutIfNeeded를 살펴보기 전에 layoutSubViews()메서드에 대해서 알아야한다.
이러한 특징 때문에 직적 호출이 아닌 layoutSubViews()을 호출할 수 있도록 예약하는 형태의 행위를 통해 호출을 유도한다.
apple 공식문서 설명
receiver(수신자)의 현재 레이아웃을 무효화하고, 다음 업데이트 주기 동안 레이아웃 업데이트를 트리거한다.
apple 공식문서 설명
레이아웃 업데이트가 보류중인 경우, 하위 View를 즉시 레이아웃한다.
SetNeedsDisplay와 SetNeedsLayout는 비동기 호출
하지만 layoutIfNeeded는 동기 호출
비동기와 동기가 이 두 메서드의 가장 큰 차이이고 layoutIfNeeded의 동기적인 성격 때문에 즉시 값이 변경되어야하는 애니메이션에 많이 사용
추가적으로 만약 main run loop에서 하나의 View가
1️⃣ setNeedsLayout 호출
2️⃣ 그다음 layoutIfNeeded 호출
3️⃣ layoutIfNeeded 호출과 동시에 즉시 View의 값들이 재계산되고 화면에 반영(두 메서드가 호출되기 전에 변경하고자 했던 모든 값들이 이 시점에서 모두 이미 반영됨)
4️⃣ 반영할 변경 값이 없어서 setNeedsLayout가 예약한 layoutSubviews는 호출되지 않음(변경된 값은 이미 layoutIfNeeded호출 시점에서 다 반영해서 없으니까)
참고
: https://developer.apple.com/documentation/uikit/uiview
: https://baked-corn.tistory.com/105
: https://zeddios.tistory.com/359
: https://developer.apple.com/library/archive/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html#//apple_ref/doc/uid/TP40010156-CH14-SW1