애니메이션과 트랜지션과 같은 조금 더 개선된 UX를 주기 위해 다양한 코드를 적었는데
원하는 대로 동작하지 않는 경우가 자주 발생하고 나는 그게 View Rendenring을 재대로
이해하지 못하고 있는 것 같다. 그래서 이번에는 생명주기의 마지막 시간은
Update Cycle에 대해 학습해보자.
앱이 실행되면 어떤 일이 일어날까?
1. main 함수가 실행된다.
2. main 함수는 UIApplicationMain 함수를 호출한다.
3. UIApplicationMain은 UIApplication 인스턴스를 생성한다.
4. Info.plist에서 필요한 데이터를 로드한다. main Nib 파일을 여기서 찾아 로드한다.
5. UIApplication은 AppDelegate 인스턴스를 생성하고 UIApplication을 위임한다.
6. UIApplication은 RunLoop를 생성합니다.
7. 준비가 완료되면 AppDelegate의 didFinishlaunchingWithOptions를 호출된다.
8. 세션에 대한 설정이 완료되면 SceneDelegate의 willConnectToSession이 호출된다.
Main Run Loop
정의
main Run Loop는 메인 스레드에서 돌아가는 run loop main thread's run loop 이다.
생성 주체
위 6번 과정을 보면 UIApplication 인스턴스가 생성하는 Run Loop가 main Run Loop이다.
과정
1. 사용자의 input(이벤트)이 발생하면 해당 이벤트를 event queue에 추가한다.
2. Application object 는 이벤트 큐에서 가장 위의 이벤트를 가져와서 event object (UIEvent) 로 변환한다.
3. 작업을 수행 할 core object에게 분배, 여기서 core object는 UIView 또는
UIViewController 같은 객체들로 이해하면된다.
4. core object안에 있는 handler를 호출하고, handler가 개발자의 코드를 호출해서 실행
5. 개발자가 작성한 코드가 return 되면 control이 handler에서 main run loop로 넘어감
6. Update Cycle 시작
Update Cycle
정의
event handling code를 모두 실행하고 control을 다시 main run loop가 갖게되면 시작된다.
이 때 layout, display 그리고 constraint와 같은 각 종 UI 상태를 업데이트하는 구간이 update cycle이다.
애니메이션 적용 중 이런 에러를 많이 접했던 경험이 있다.
여기서 뷰를 hidden하는 코드, 즉 UI 상태를 업데이트 할 때 이런 경고 문구가 뜨게 되는데 이유가 뭘까??
정답은 thread마다 run loop가 존재하는데, 만약 여러개의 thread에서 UI를 갱신하게된다면
각각의 run loop의 update cycle이 달라 개발자가 원하는 동작을 제어하기 힘들다.
그렇기 때문에 UI는 꼭 main thread에서 갱신을 하라는 경고이다.
과정
1) Layout
View의 크기 , View 위치에 대한 것을 의미한다.
Layout을 업데이트하는 방법은 크게는 2가지 방법으로 나뉜다.
첫번 째는 다음 update cycle 때 자동적으로 update flag를 남기는 방법이 있다.
남기는 방법은 대표적으로 다음과 같다.
- subview 추가
- view 크기 변경
- view constraint qusrud
- UIScrollView 스크롤
- 그 외 ..
위 이벤트들이 발생하면 다음 update cycle 때 시스템이 layoutSubviews()를 호출해서 변경사항을 적용한다.
두번 째는 개발자가 직접 호출하는 방법이다.
비용이 높은 함수 순으로 살펴보자.
a) layoutSubviews()
// 현재 view와 subviews들의 layoutsubviews()가 모두 호출되어, 비용이 굉장히 크디.
// 개발자가 직접 호출은 하지 않는다.
func layoutSubviews()
b) layoutIfNeed()
// 시스템이 바로 layoutSubviews()를 호출한다.
// 호출한 view에 dirty flag가 켜져 시스템에 바로 변경사항을 적용하라고 전달.
func layoutIfNeeded()
view가 refresh 되어야 함을 가리키는 것들이 있으면 layoutSubviews가 불리지만
시스템이 필요성을 느끼지 못하면 layoutIfNeeded가 아무리 있어도 layoutSubviews를 호출하지 않는다.
언제 사용하는게 좋을까 ?
- 당장의 layout에 의존 된 업데이트를 해야해서 다음 update cycle을 기다릴 수 없을 때
- constraints에 애니메이션을 적용할 때 ⇒ 애니메이션 코드 블럭이 불리기 전에 layoutIfNeeded를 호출해서
업데이트 예정인 모든 layout을 다 적용하고constraint를 수정한 후 애니메이션 블럭 안에
layoutIfNeeded를 다시 불러서 애니메이션을 적용
c) setNeedsLayout()
// layout의 업데이트를 다음 update cycle에서 적용
func setNeedsLayout()
update cycle에서 layoutSubviews()가 불릴 때 해당 view와 그 subview들에 모두 적용 되고
layoutSubviews()를 호출할 수 있는 가장 저렴한(부하가 적은) 방법이다.
2) Display
Color, Text, Image, CG Drawing과 같이 size, position과 관련 없는 상태 값들을 통틀어 의미한다.
display도 마찬가지로 자동 update flag로 하는 방법과 개발자가 직접 호출하는 방법이다.
draw(_:) 함수
layoutSubviews 처럼 동작하지만 재귀적으로 하위 view들에게는 적용되지 않는다는 차이점이 있다.
자동 update flag
- view의 bounds 변경
개발자가 직접호출하는 방법
a) setNeedsLayout()
// 업데이트는 다음 update cycle에서 적용한다.
func setNeedsDisplay()
다음 update cycle에서 flag가 켜진 모든 view들에 대해 draw(:_)를 호출하게된다.
대부분의 경우 UI components에 변화가 생기면 자동으로 다음 cycle에 redraw를 한다.
그렇지 않은경우가 있을까?
UI Components에 직접적인 연결이 되어 있지 않는 custom drawing은 setNeedsDisplay를 지정해줘야한다.
3) Constraint
View의 Layout을 정의하고 있는 규칙이다.
자동 update flag
- view 계층에서 view 삭제
- constraints 활성화 / 비활성화
- constraints들의 우선순위(priority)나 상수값 변경
개발자가 직접호출하는 방법
a) updateConstraints()
// layoutsubviews()와 비슷한 역할 모두 호출되어, 비용이 굉장히 크디.
// 개발자가 직접 호출은 하지 않는다.
func updateConstraints()
b) updateConstraintsIfNeeded()
// updateLayoutIfNeeded와 비슷하게 생겼으니 다음 cycle을 기다리지 않고 바로
// updateConstraints를 호출한다.
func updateConstraints()
updateConstraintsIfNeeded가 불리면 update flag를 확인한 후 update가 필요하다고 판단되면
updateConstraints를 바로 호출하는 메소드다.
역시 시스템에 부하가 많이 가는 메소드에 속한다.
c) setNeedsUpdateConstraints()
func setNeedsUpdateConstraints()
Constraint가 다음 update cycle에서 업데이트 할 수 있음을 보장한다.
update cycle 정리
메소드의 목적 | Layout | Display | Constraint |
업데이트 (직접 호출 금지) |
layoutSubbviews | draw | updateConstraints |
바로 업데이트 요청 (비용 비쌈) |
layoutIfNeed | updateConstraintsIfNeeded | |
다음 cycle에 update 요청 | setNeedsLayout | setNeedsUpdateConstraints | |
자동으로 update flag 켜기 | 기기회전 subviews 추가 view 크기 변경 UIScrollView 스크롤 view의 constraint 변경 |
view bound 변경 | view 삭제 constraint 상수값 변경 constraint 우선순위 변경 constraint 활성화 / 비활성화 |
updateCycle 실행순서
updateConstraints > layoutSubviews > draw
참고
'iOS > UIKit' 카테고리의 다른 글
frame vs bounds (2) [ 정의, 기준점, 크기 계산 방식] (0) | 2024.09.02 |
---|---|
frame vs bounds (1) [ CGPoint, CGSize, CGRect ] (0) | 2024.09.02 |
생명주기 (3) [ View 생명주기 ] (0) | 2024.09.01 |
UIKit 코드 베이스 셋팅 (0) | 2024.09.01 |
생명주기 (2) [ ViewController 생명주기 ] (0) | 2024.08.31 |