티스토리 뷰

iOS

iOS Widget 구조의 구체적 이해

꿀벌의달콤한여행 2023. 6. 17. 00:16

이번 WWDC 2023 영상 중 위젯 구조에 대해 짧지만 상세히 다룬 세션(Bring widgets to life, 8분 ~ 10분)이 있었습니다.

평소 샘플 코드 구조를 흉내내며 위젯을 구현했었던 과거가 부끄러울 정도로... 제가 이해했던 것과 달라서 공유차 영상 내용을 정리합니다.

 

 

위젯을 구현하기 위해선, 먼저 Xcode에서 Widget Extension을 정의해야 합니다.

이 Widget Extention은 시스템이 확인하여 독립적인 프로세스로 동작(run)시킵니다.

정의된 Widget Extension

 

 

위젯은 TimelineProvider를 정의합니다.

이 TimelineProvider는 일련의 Entries를 리턴하고, 각각의 Entry는 실제 위젯의 모델이 됩니다.

위젯이 Visible해지면, 시스템이 Widget Extension Process를 실행(launch)시키고 위젯의 TimelineProvider에게 Entries를 요청합니다.

Widget Extension의 TimelineProvider에게 Entires를 요청

 

 

요청된 Entries는 View Builder에게 공급됩니다.

(여기서 View Builder란 Widget Configuration의 일부분, 즉 우리가 코드로 작성한 위젯의 뷰 부분을 의미합니다).

요청된 Entires는 View Builder에게 공급

 

 

View Builder는 공급받은 Entries를 사용해 일련의 Views를 생성합니다.

생성한 Views의 표현(Representation)은 디스크에 아카이빙합니다.

Views 생성 후, 디스크에 아카이빙

 

 

특정 Entry를 표시할 시간이 되면, 시스템은 디스크에 아카이브 해뒀던 위젯의 표현을 프로세스에서 디코딩하고 렌더링합니다.

이게 의미하는 건, 우리가 작성한 위젯의 View Code가 아카이빙을 하는 동안에만 실행된다는 걸 의미합니다.

즉, View의 표현은 시스템 프로세스에 의해서 렌더링 된다는 의미입니다.

시스템 프로세스에 의해 디코딩 및 렌더링 되는 뷰

 

 

다만 데이터가 정적이지 않다면 Entries를 업데이트하고 싶을 때가 있습니다.

그럴 땐 아래와 같이, 위젯에서 보여줄 데이터를 업데이트 할 때마다reloadTimelines(ofKind:) 메소드를 호출하면 됩니다.

이 메소드를 호출하면 지금까지 설명했던 과정이 반복됩니다.

Entries를 재생성하고, 새로운 Views를 복사해서 디스크에 아카이빙합니다.

 

 

마지막으로 위젯 아키텍처에서 핵심사항 3가지가 있습니다.

먼저 첫 번째인 'Widgets are rendered in a separate process' 입니다.

이말은, 위젯이 보이는 순간에는 우리가 작성해둔 코드가 실행 중이 아니라는 의미입니다.

보이는 순간보다 더 앞선 시간에 이미 View Builder가 실행됐고, 생성된 뷰는 디스크에 아카이빙 됩니다.

사용자에게 보이는 위젯은 이미 생성된 뷰가 렌더링된 것일 뿐입니다.

 

두 번째는 'Changes are driven by timeline entries' 입니다.

위젯 콘텐츠에 대한 변화들은 우리가 TimelineEntry를 업데이트 함으로써 가능하다는 의미입니다.

 

마지막 세 번째는 'Reloads from interactions are guranteed'입니다.

이는 이번 iOS 17부터 위젯이 interactive 해졌기에 추가된 내용입니다. 

iOS 17부터 위젯은 Button, Toggle을 사용하여 사용자 인터렉션을 줄 수 있습니다.

보통 위젯에 대한 업데이트는 best effort basis로 완료되는데, 인터렉션에 의한 리로드는 항상 발생이 보장됨을 의미합니다.

Widget architecture Key takeaway

테스트

여기까지는 WWDC 영상을 쭉 따라간 것에 불과하고, 실제로 코드로 동작을 확인해보겠습니다.

사실 영상을 보면서 궁금했던 부분은, 우리가 보는 위젯의 뷰는 이미 디스크에 아카이빙 됐고 특정 시간대가 되면 디코딩되어서 렌더링된다는 부분입니다.

 

즉, 위젯을 구성하기 위해 작성된 뷰 코드는... (제 망상이었지만) 특정 Entry의 시간이 될 때마다 실행되면서 뷰를 구현하는 것이 아닌, TimelineProvider에 의해서 Entry가 공급되는 순간에 다 실행되어 뷰를 만드는 것입니다.

그리고 그 뷰들은 디스크에 아카이빙 되어 특정 시간이 되면 시스템 프로세스에 의해 디코딩 및 렌더링되는 것이죠.

 

테스트를 통해 풀고자 하는 의문은 이렇습니다.

"우리가 위젯을 구현하기 위해 작성한 View 코드는 특정 Entry 시간마다 실행될까? 아니면 TimelineProvider가 Entry를 공급할 때만 실행될까?"

 

제 망상(특정 Entry 시간 때마다 뷰 코드가 실행될 거야!)이 틀림을 증명하고, 영상 내용(위젯뷰는 디스크에 아카이빙되고 이때 뷰코드가 실행됨. 그리고 특정 Entry 시간 때마다 시스템 프로세스가 디코딩 및 렌더링)이 진짜임을 증명하는 게 목적입니다.

 

이를 확인하기 위해 샘플 프로젝트를 만들었습니다.

(애플이 프로젝트 생성시 제공해주는 코드를 베이스로 만들었습니다)

 

먼저 위젯을 구성하는 코드입니다.

공급받은 Entry의 date, count를 그려주는데, 여기에 추가로 GroupUserDefaults.shared.count를 그려줍니다.

Widget을 구성하는 코드

 

 

GroupUserDefaults는 UserDefaults를 래핑한 클래스입니다.

App Group을 이용해 앱과 Widget Extension간 데이터를 공유를 도와줍니다.

GroupUserDefaults

 

이제 위젯을 그리는 데에 필요한 Entry를 공급하는 TimelineProvider입니다.

각 Entry는 1분의 간격을 가지고, (중요) 현재 앱과 공유하는 count 값으로 생성됩니다.

3개 정도 만들고, Timeline policy는 .atEnd로 설정하여 3분이 지나면 Entry를 다시 요청하도록 작성했습니다.

TimelineProvider

 

이제 최초로 위젯을 홈화면에 추가하면 아래와 같이 값이 0으로 보일 것입니다.

최초 추가된 위젯

 

 

이후 앱을 열어 버튼을 눌러 UserDefaults count를 마구 증가시켜줍니다.

버튼을 11번 눌러 0 -> 11로 값을 업데이트 해줬습니다.

 

 

이제 특정 Entry로 위젯이 렌더링될 때마다 관찰하면 됩니다.

지금은 Entry 간격이 1분이니, 1분마다 업데이트되는 Time을 보며 밑의 값을 관찰하면 되겠습니다.

Time이 바뀐다는 것은 곧 다른 Entry를 의미합니다.

만약 Entry가 공급될 때마다 뷰가 렌더링된다면, Count from en... 값이 0 일때, Direct count는 11이어야 합니다.

하지만 Entry가 공급될 때 뷰가 전부 렌더링 되어 디스크에 아카이빙 되었다면, 두 값은 항상 동일해야 합니다.

딴짓하다 47분 캡처를 놓쳤습니다
시간이 바뀐 것을 보니 앞과는 다른 Entry임을 알 수 있습니다.
TimelineProvider가 공급한 Entry가 모두 소진되어, 설정해둔 policy(.atEnd)에 맞춰 Entry가 새로 공급되었습니다.

 

결과를 보면 Count from en.. 값과 Direct count 값은 Entry가 바뀌어도 0으로 유지되다가, 같이 11로 업데이트 되었습니다.

이는 위의 영상에서 설명한 것과 동일하게, Entry가 공급될 때 뷰가 렌더링 되어 디스크에 아카이빙되었다가 특정한 때에 맞춰 디코딩 후 렌더링되는 것을 의미합니다.

즉, 반복하자면 우리가 작성한 위젯 View 코드는 디스크에 아카이빙되는 그 순간에만 실행된다는 의미입니다.

 

결론

iOS 위젯은 일반적으로 기대하는 것과는 조금 다르게 동작합니다.

보통은 사용자가 보는 위젯 뷰가 업데이트 될 때마다 뷰 코드가 실행되고, 새로 그려진다고 생각합니다.

하지만 실제론, 사용자가 보는 위젯 뷰는 이미 디스크에 아카이빙된 걸 그려주는 것에 불과합니다.

다시말해 위젯 View 코드는 위젯뷰가 그려질 때마다 실행되는 것이 아닌, 디스크에 아카이빙 될 때 실행됩니다.

 

특히 다이나믹한 데이터를 기반으로 위젯을 구현할 때는 이러한 점을 고려하여, Timeline Entry를 중심으로 위젯 리로드 로직을 생각해야합니다.

What's next?

사실 위젯 인터렉션이 궁금해서 wwdc 영상을 본 거라... 이후 위젯 인터렉션도 자세히 봐야겠습니다.

 

참고자료

https://developer.apple.com/videos/play/wwdc2023/10028/ 

https://developer.apple.com/documentation/widgetkit/keeping-a-widget-up-to-date

https://developer.apple.com/documentation/widgetkit/timelineprovider

댓글