본문 바로가기
iOS

iOS의 뷰가 그려지는 과정

by SeoB-P 2022. 6. 19.

서론

iOS개발을 하다가 보면 가끔 View가 말을 듣지 않을때가 있다.

분명 원하는 값을 다 주었는데도, 이게 왜 안보여? 이건 또 왜이렇게 보여?

View를 디버깅하기위해서 계속해서 앱을 껏다 켰다 하는 시간은 우리의 개발시간을 상당히 잡아먹는다.

View가 그려지는 과정을 이해함으로써 이 시간을 줄여보자.

 

Run Loop

View가 그려지는 과정을 이해하기 위해서는 Run Loop의 개념을 이해해야 한다.

Run Loop의 정의를 보자. 

input sources들이 manage되는 프로그래밍적인 interface이다. 

여기서 input sources란, 키보드, 마우스, 소켓 등등의 사용자가 어떠한 입력을 하는 것을 말한다.

따라서, Run Loop는 모든 사용자의 입력을 처리하는 객체라고 할 수 있다.

(Timer는 input source는 아니지만 이또한 Run Loop가 처리한다.)

 

특징 및 사용처

Run Loop는 (Thread의) input source 및 Timer를 처리한다.

따라서, 밑과 같은 특징을 가진다.

  • Thread는 각자의  Run Loop를 가진다.
  • Thread를 생성할 때 Run Loop는 자동으로 생성된다.
  • Thread를 생성을 할 때 Run Loop는 자동 실행이 되지 않는다.(Main은 예외)
    • Main Thread는 프레임워크 차원에서 어플이 실행될때 자동으로 Run Loop 실행.

애플문서: Run Loop를 참고하면 현재 Thread의 RunLoop를 가지고 오거나 실행 시킬 수 있다.

 

작동 원리

앞서 Run Loop는 Input SourceTimer를 처리한다고 했다.

  • Input Source: 다른 Thread나 Application에서 온 비동기 이벤트 전달
  • Timer: 예약된 시간 또는 반복된 간격으로 발생하는 동기 이벤트 전달

출처 :https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html

 

View가 그려지는 과정

Run Loop에 대해서 대략 알았으니 View가 그려지는 Cycle을 알아보자.

출처: https://jeonyeohun.tistory.com/336?category=881841

먼저, 어플리케이션과 유저와의 상호작용은 Event Queue에 추가된다.

Application 객체는 Event Queue에서 작업을 가지고와서 앱 내에 다른  object들에게 Event를 dispatch한다. 

사용자의 입력 이벤트를 해석하고, Core Object에서 해당 입력에 대한 handler를 호출하여 Run Loop를 실행한다.

(이 Handler가 개발자가 작성한 코드를 호출할 것임.)

호출 됬던 Handler들이 리턴이 되면 제어권이 다시 Main run Loop로 돌아오며 Update Cycle이 시작된다.

 

Update Cycle?

Update Cycle이란 앱이 모든 Handler를 실행 뒤에 제어권이 다시 Main Run Loop(시스템)로 돌아 오는 시점에 시작하는 Cycle 을 의미함.

여기서 모든 이라는 키워드를 잘 기억해 두자.

 

이 시점에서, iOS 시스템은 Layout, Constraint, Display 등을 업데이트 하기 시작함.

즉, UI가 바뀌는 시점

Handler를 처리하는 동안에 View를 다시 그려야 한다고 요청이 되어있으면 시스템은 View를 다시 그려야 한다고 Marking을 해둠.

Marking을 해둔 요청들은 다음 Update Cycle때 처리가 된다.

 

사용자의 입력이 즉각처리되는게 아니라, 다음 업데이트 Cycle에 처리가 되면 View가 너무 늦게 변경되는거 아닌가?🤔

Nop! iOS앱들은 기본적으로 초당 60번의 Update Cycle이 발생됨.

따라서, 사용자들은 거의 느낄 새가 없음.

 

앞에서 모든 Handler가 실행 된 뒤의 시점이 Update Cycle의 시점이라고 했음.

엄연히 이벤트가 전달되는 시점과 처리되는 시점이 차이가 있기 때문에, 원했던 모양으로 View가 업데이트 되지 않을 수 있음.

애니메이션같이 실시간으로 View의 속성이 변하고 그 값을 기준으로 다시 그려야 하는 경우가 대표적임.

보통 이 과정에서, 우리가 프로그래밍한 View들이 제대로 처리가 안되는 경우가 많았다고 생각이 듬.

그러면 이 문제를 어떻게 해결하냐?

UIView에 내장된 여러 메서드를 이용하면 해결 가능.

 

출처: https://tech.gc.com/demystifying-ios-layout/

Update Cycle을 제어하는 방법

Layout

Layout(위치, 크기)을 변경 시키는 방법

  • layoutSubviews()
    • 호출한 뷰와 하위에 있는 모든 subView의 위치와 크기를 변경하도록 함.
    • frame의 크기와 위치가 변경될 때마다 업데이트 할 UIView를 마킹
    • 사이클이 시작되서 호출이 되면 업데이트가 시작됨.
    • 모든 SubView들도 다 알려줘야하기 때문에 비용이 큼.
    • 외부에서 직접적으로 호출금지, 오버라이딩 할것.
    • 이 메서드가 completed 되면 DidLayoutSubviews가 VC에서 호출됨 
    • layoutSubviews가 안정적으로 호출되는 유일한 곳이므로 만약 Layout에 관한 로직이 있다면 여기에다가 넣기
  • setNeedsLayouts()
    • LayoutSubView를 호출하는 가장 저렴한 방법
    • 다시 계산해야 함을 시스템에 알림
    • 즉시 실행 및 반환되지만 업데이트가 바로 되진 않고 다음 사이클에 layoutSubviews를 호출하고 업데이트됨
  • layoutIfNeeded()
    • 다음 계산까지 가지 않고 바로 LayoutSubViews를 호출함.
    • 하지만 layoutIfNeeded를 호출 한 뒤에 View업데이트를 지시하지 않으면 LayoutSubViews가 호출 되지 않음.
    • 강제로 사이클을 깨는 것이기 때문에 비용이 비쌈.
    • 애니메이션할때 주로 사용.

Automatic refresh triggers

그러면 개발자가 일일이 layoutSubviews를 호출해야하냐? No.

보기를 레이아웃이 변경된 것으로 자동으로 표시하는 여러 이벤트가 있다.

개발자가 수동으로 이 작업을 수행하지 않고도 layoutSubviews가 다음 사이클에 불림.

  • 뷰의 크기를 resizing했을때
  • subView를 add했을때
  • Scroll했을때
  • Device를 회전했을때
  • View의 Constraint를 Update했을때.(오토레이아웃 줄때)

우리가 프로그래밍할때 주로 addSubView나 .frame()을 쓰곤하는데, 그때 마다 마킹이 되고 다음 사이클에 업데이트가 되었던 것임! 

 

Display

Layout과 관련없는 것들(색상, 텍스트, 이미지...)

  • draw(_:)
    • 호출한 View의 Display를 그림.
    • LayoutSubView와 같이 작동하지만 하위 뷰에서 까지 호출되지는 않음.
    • LayoutSubView와 마찬가지로 절대로 외부에서 호출하지 말것.
  • setNeedsDisplay()
    • 다음에 그려질 Display가 있다고 마킹 후 반환.
    • setNeedLayout과 거의 동일한 역할을 함.

Display에서는 LayoutIfNeed처럼 바로 바꾸는 메서드는 없음.

Constraints

AutoLayout에서  View를 Layout하고 Display하는데에는 세가지 단계가 있음.

 

Constaint -> Layout -> Display

제약조건(constarint)를 설정하고, 그에맞는 Layout을 잡고, Display 함.

그래서, AutoLayout을 설정하는것은 Frame값을 주어서 Layout을 잡는 것보다 비싼 연산이라고 함.

  • updateConstraints()
    • 오토레이아웃의 Constarint를 업데이트 사이클에서 바꾸는 메서드
    • 호출 X 오버라이드 O
    • Constarint가 바뀌면 View가 마킹됨
    • 이 역시 layoutSubview와 아주 흡사함. 
  • setNeedsUpdateConstraints()
    • 다음 사이클에 업데이트될 View를 마킹함.
  • updateConstraintsIfNeeded()
    • Constarint를 바로 업데이트함.
  • invlidateIntrinsicContentSize()
    • Label처럼 IntrinsicContentSize를 가지는 애들(Label.,.)등에 사용
    • 수동으로 IntrinsicContentSize를 바꾸었다면, 시스템에게 Size를 알려줘야 하기 때문에 사용
    • 다음 사이클에 IntrinsicContentSize를 바꾸라고 마킹.

 

정리

복잡한 위 내용을 잘 정리 및 번역하신분이 있어서 가지고 왔다.

 

https://jeonyeohun.tistory.com/336?category=881841

 

참고 사이트를 많이 참고했습니다 꼭 읽어보시기 바랍니다 🙇‍♂️

 

 

참고

https://babbab2.tistory.com/68

https://tech.gc.com/demystifying-ios-layout/

https://jeonyeohun.tistory.com/336?category=881841 

https://medium.com/@Alpaca_iOSStudy/viewcontroller-view-lifecycle-daed5766e02b

'iOS' 카테고리의 다른 글

iOS Bluetooth  (1) 2022.10.07
Method Swizzling in iOS  (0) 2022.07.10
서버없이 Networking Test하기 with URLProtocol  (2) 2022.05.15
iOS Cache  (2) 2022.04.24
Responder Chain  (3) 2022.03.29

댓글