iOS ) Layout Cycle / The Deferred Layout Pass
안녕하세요 :) Zedd입니다.
constraints 관련 메소드 공부하는데.."layout pass"라는 말이 나오더라구요.
뭔지 모르겠어서 공부하려고 합니다.
# The Deferred Layout Pass
영향을 받는 view의 프레임을 즉시 업데이트 하는 대신,
Auto Layout은 가까운 미래에(near future) layout pass를 예약(schedules)합니다.
이 지연된 Pass(deferred pass)는
1. layout의 constraints를 업데이트 한 다음,
2. view 계층 구조의 모든 view에 대한 프레임을 계산합니다.
setNeedsLayout()또는 setNeedsUpdateConstraints()를 호출하여 own deferred layout pass을 예약할 수 있습니다.
무슨소리인지 잘 모르겠으니. 그림으로 봅시다.
# Layout Cycle
Layout Cycle입니다.
constraints가 변경될 때 까지 Application Run Loop가 반복되고 있습니다.
그러다
constraints에 변경이 생기면!!
이로 인해 deferred layout pass가 예약됩니다.
"예약"했으니 언젠가 layout pass가 실행이 될거잖아요?
어케어케 실행이 되고, layout pass가 완료되면, view 계층 구조를 보고 view의 모든 프레임이 업데이트 됩니다.
대충 흐름만 봤고, 하나씩 살펴봅시다.
# Constraint Changes
Application Run Loop는 계속 도는거니까..Constraint Changes부터 봅시다.
유명한 그림이죠!
내가 생성한 Constraint는 수학적 표현식으로 변환되고 레이이웃 엔진 내에 유지됩니다.
위 표현식에 영향을 미치는 모든 것이 "Constraint를 변경한다"라고 말할 수 있어요. 여기까지는 이해가 가죠!?
위 표현식에 영향을 미치는 행위에는
1.Activating or deactivating a constraint(constraint 활성화 또는 비활성화)
2. Changing the constraint’s constant value(constraint의 상수값 변경)
3. Changing the constraint’s priority(constraint의 prioirty변경)
4. Removing a view from the view hierarchy(view계층에서 view 제거)
이런것들이 있을 수 있어요.
1 ~ 3번은 어 그래! 이렇게 하면 저 표현식이 바뀌겠군! 느낌이 확 오지만
4. Removing a view from the view hierarchy(view계층에서 view 제거)?
Q : view계층에서 view를 제거하는거도 저 표현식에 영향을 줘?
A : 네 간접적으로 constraint 변화를 야기할 수 있기 때문에 이것도 constraint 변화라고 봅니다.
자 그럼 Constraint가 변경이 되면 어떤일이 일어날까요?
가장 먼저 일어나는 일 : 레이아웃 엔진이 레이아웃을 다시 계산한다.
레이아웃을 다시 계산하게 되면, 표시되고 있는 view에 알림이 전송되고 superView가 레이아웃이 필요한 것으로 표시(mark) 됩니다.
이것이 실제로 deferred layout pass가 예약되는 원리에요.
예제로 한번 더 봅시다.
가장 왼쪽 그림의 최상단에 있는 Show Extra Options를 체크 해제하면,
가장 오른쪽 그림처럼 view의 크기가 줄어들어야 합니다.
1. Show Extra Options가 선택되어있음.
2. Show Extra Options를 체크 해제.
3. Constraint를 변경하는 코드를 넣었겠죠? Constraint변경이 일어남.
4. 레이아웃 엔진이 레이아웃을 다시 계산.
5. 내 superview가 다시 레이아웃이 필요하다고 표시(mark) (== superview.setNeedsLayout()) == deferred layout pass가 예약됨
딱 여기까지가 두번째 그림까지 입니다.
분명히 나는 Show Extra Options를 체크 해제했지만 아직 view의 크기가 변하지 않았죠.
여기까지는 레이아웃엔진에서 프레임이 실제로 변경되었지만, view 계층 구조에서는 아직 변경되지 않은 것입니다.
이제 deferred layout pass가 오면, 맨~위에서 그랬죠?
1 ) layout의 constraints를 업데이트 한 다음,
2 ) view 계층 구조의 모든 view에 대한 프레임을 계산합니다.
6. deferred layout pass가 완료되면, 마지막 그림처럼 올바르게 변경됨.
이해가셨나요?
자 이제 다음 단계인 Deferred Layout Pass로 넘어가봅시다.
# Deferred Layout Pass
자. 계속 말했지만..
이제 deferred layout pass가 오면
1 ) layout의 constraints를 업데이트 한 다음,
2 ) view 계층 구조의 모든 view에 대한 프레임을 계산합니다.
라고 했습니다.
Deferred Layout Pass는 실제로 view 계층 구조를 통과하는 2개의 Pass를 포함합니다.
-
The update pass updates the constraints, as necessary == Update Constraints
-
The layout pass repositions the view’s frames, as necessary = Reassign view frames
입니다.
즉, update pass + layout pass = Deferred Layout Pass인거죠.
주의할 점은 as necessary라는 거..(필요에 따라 할수도 있고 안할 수도 있다는 것 같아요.)
"Pass"는 사실 약간 잘못된 이름이라고 해요? (a pass is actually a little bit of a misnomer)
#1. Update Constraints(= Update Pass)
시스템은 view 계층 구조를 탐색(traverses)하고
모든 ViewController에서 updateViewConstraints()를 호출하고,
모든 View에서 updateViewConstraints()를 호출합니다.
참고로 탐색은 아래에서 위로 일어납니다.
Mysteries of Auto Layout, Part 2에서는
view는 그들의 update constraints method를 호출하도록 명시적으로 요청해야한다고 나와있습니다.
그리고 그 메소드가 setNeedsUpdateConstraints()라고...
setNeedsUpdateConstraints()는 결국 updateViewConstraints()를 호출하니, 둘 다 똑같은 말입니다.
아무튼..
1. deferred layout pass시작
2. layout의 constraints를 업데이트 해야함
3. setNeedsUpdateConstraints() 호출
4. near future에 updateViewConstraints()가 호출됨.
이라고 보면 될 것 같습니다.
위 단계들은 view가 다음 Layout Pass에 맞춰 Constraints를 변경할 수 있는 방법이지만, 실제로 필요하지 않은 경우가 더 많다고 합니다.
1. 이상적으로, 모든 초기 constraints 설정은 Interface Builder 내에서 발생
2. constraints을 프로그래밍 방식으로 할당해야하는 경우에는 viewDidLoad같은 위치가 훨씬 좋음.
하지만 Constraints변경이 너무 느리다거나 view가 중복변경을 한다면
updateViewConstraints()를 override하여 구현하는것이 도움이 될 수 있습니다.
updateViewConstraints()내에서 Constraints를 변경하는 것이, 다른 시간에 constraints를 변경하는 것보다 실제로 더 빠르다고 합니다.
이유 : 엔진이 이 패스에서 발생하는 모든 Constraint 변경사항을 배치로 처리할 수 있기 때문.
예를들어,
1. configuration변경으로 인한 constraints rebuild가 필요할 때
2. view가 constraint를 여러번 다시 rebuilding.
3. 많은 낭비.
== > 해결 : Constraint가 있는 view에서 setNeedsUpdateConstraints()호출. 그런 다음 view의 updateConstraints()를 재정의하여 영향을 받는 Constraints수정.
자 아무튼! 이렇게 Update Constraints(= Update Pass)가 끝나면 Costraints는 모두 최신상태이며
View위치를 변경할 "준비"가 된 겁니다.
#2. Reassign view frames(= Layout Pass)
시스템은 view 계층 구조를 탐색(traverses)하고
모든 ViewController에서 viewWillLayoutSubviews()를 호출하고,
모든 View에서 layoutSubviews()를 호출한다고 해요.
Mysteries of Auto Layout, Part 2에서는
시스템은 view 계층 구조를 탐색하고 (위에서 아래로)
레이아웃이 필요하다!고 mark된 모든 view에서 layoutSubviews를 호출합니다.
Mysteries of Auto Layout, Part 2ㅇㅔ서
updateConstraints말할 때 아래에서 위(bottom up)로 탐색한다..라는 말은 안하고
요 Layout이야기 할때만 위에서 아래로(top down) 라는 말을 하네요..!!
정리하면..
1. layout pass진입
2. 시스템은 viewController와 view계층을 위에서 아래로 탐색
3. 레이아웃이 필요하다!고 mark된 모든 view에서 layoutSubviews를 호출.
이 되겠죠..?
기본적으로 layoutSubviews는 레이아웃 엔진에서 계산한 직사각형으로, 각 Subview들의 frame을 업데이트 한다고 해요.
자..Subview들의 frame이 결정이 되고 나면, 그제서야 오른쪽 그림처럼 view가 업데이트 된 것을 볼 수 있습니다.
주의 :
- Constraints를 사용하여 표시 할 수 없는 레이아웃이 필요한 경우에만 layoutSubviews를 override해야합니다.
- layoutSubviews를 override하기로 선택했다면, 우리는 현재 Layout ceremony(세레모니..?)중이라는 것을 명심해야함.
- 일부 view는 이미 배치되어있고, 다른 view는 아직 배치되지 않았지만 곧 될 것 같은 그런~~~ 대충 섬세한 순간(delicate moment)이라는 뜻
따라서 따라야 할 몇가지 규칙이 있음.
Do / Don`t를 잘 구분해서 볼 것!
# layoutSubviews()를 override할 때 주의 할 점
Do
1. super.layoutSubviews()를 호출해야합니다.
2. subtree 내에서 view의 레이아웃을 무효화(invalidate)하는 것도 좋다. 하지만 super호출 전에 해야함.
Don`t
1. 내부에서 setNeedsUpdateConstraints()를 호출하지 말것.
2. subtree 외부에서 레이아웃 무효화(invalidate)는 하면 안된다 -> 레이아웃 피드백 루프가 발생할 수 있음.
3. layoutSubviews내부에서 Constraints를 수정하는 경우가 있음. 이거 괜찮기는 한데..주의해야함.
➞ Constraint를 수정할 때 계층 구조의 다른 view가 영향을 받을 수 있는 사항을 예측하기 어려울 수 있음.
따라서 Constraints를 변경하는 경우, 실수로 subtree외부의 레이아웃을 무효화(invalidate)하기 매우 쉬움.
== 2번에서 주의 한 것.
자! 이렇게
-
The update pass updates the constraints, as necessary == Update Constraints
-
The layout pass repositions the view’s frames, as necessary = Reassign view frames
2가지 pass를 끝내고 나면, layout cycle이 완료되고 모든게 올바른 위치에 있고 constraint변경사항이 완전히 적용됩니다!!!
# Layout cycle에 대해 기억할 것
1. Constraints를 수정 할 때 view프레임이 즉시 변경될 것으로 기대하지 말것.
2. layoutSubviews를 override해야하는 경우, 디버깅이 어려울 수 있으므로 레이이아웃 피드백 루프를 피하도록 주의 할 것.
글에 적은 모든 내용은 아래 참고 문서들을 기반으로 작성되었습니다.
혹시라도 틀린 내용이 있다면 댓글 달아주시면 감사하겠습니다.
도움이 되었으면 좋겠네요 :D
참고 :