본문 바로가기
WWDC

WWDC20 - Keep your complications up to date

by vapor3965 2021. 8. 9.

목차


    • 세션보면서 정리한 내용입니다. 해석이 잘못된 경우가 있을수있으니 발견하시면 댓글로 남겨주시면 감사하겠습니다🙏🏻

     

    https://developer.apple.com/wwdc20/10049

     

    Keep your complications up to date - WWDC20 - Videos - Apple Developer

    Time is of the essence: Discover how your Apple Watch complications can provide relevant information throughout the day and help people...

    developer.apple.com

     


    요약

    complciation을 최신상태로 유지하는 방법은 크게 2가지 방법이 있고, 세부적으로는 4가지방법이 있다. 

    • foreground일때, background일때

     

    forground일때

    • complicationServer를 호출하여 reload하면된다.

    background일때.

    • backgroundAppRefresh를 이용한다.
      • 한시간마다 4번까지 사용할 수 있고, 지정한 시간은 보장되는 시간이 아니다. 
      • 제출을 한후, 해당작업을 처리하는 핸들러를 구현하고, 완료후 다시 제출하도록하고, 완료했다라고 호출한다
      • 중요한점은 네트워킹은 안된다. 
    • background URLsession을 이용한다. ( 조금 복잡함 ) 
      • 네트워킹이 필요할때. 
      • 마찬각지로 한시간마다 4번가능하다. 
      • 앱이 시작할때 처음에 URLSession을 백그라운드로 호출한다. 
        • 그다음 마찬가지로,  완료후, 다시 호출하도록 한다.
    • complications push를 이용한다. 
      • 서버가 애플서버로 푸시하여, 애플서버가 watch로 푸시하는 방법이다.  하루에 각 watch마다 총 50번 받을 수 있다. 
      • 그러기위해서는 identifier를 생성해야한다 ( 포맷이 일치해야함 ) 그리고 만든 identifier로 certificate를 생성한다. 
      • 그리고 앱프로젝트에서  background mode - Remote notification을 체크한다. 
      • PKPushRegistry를 이용하여 credential을 받고, 이를 서버에 로드하고, 서버는 이를 활용하여 애플 서버에 푸시한다. 
        • 푸시를 보낼때는 포맷을 유지해야한다 ( "aps" - "content-available" ) 

     


     

     

     

     

    아래와같은 내용들을 다뤄볼것이다. 

     

     

     

     

    앱으로는, 연날리기 watch app을만들것이고,

    핸드폰없이도 독자적으로 사용할수있도록할것이다.

    멀티 complication을 사용하여, 각각 정보를표현하도록할것이다. - HeathKit으로부터 activity 정보를 얻고

    날씨정보도얻고, 친구들정보를얻는다. 

     

     

     

    우선, complication을 업데이트하기위해서는, ClockKit API를 이용해야한다. 

     

     


     

    앱이 포그라운드에 있을때, complication을 업데이트하는 방법은 간단하다. 

    ( 앱에서 사용자가 직접데이터를 변경하거나, 또는 포그라운드에있을떄 새로운데이터를 받는경우나, ) 

    complicationServer에게 reload해달라고 부탁하여, 현재 entry를 새로반환하면된다. 

     

     

    아래코드는  updateActiveComplications를 작성한 코드다.

    모든 complication들을 각 시간에맞춰 update하는 코드다. 

    현재 complication들을 가져와서, for문을통해 reloadTimeline을 호출하는데,

    이때, . 정말 업데이트가 필요한 complication들만 ! 선택적으로 호출해야한다.

     

     

    updateActiveComplication에의해 reloadTimeline이호출되고나면,

     CLKComplicationDataSource - getCurrentTimelineEntry 가 호출된다. 

    app에서는 timeline Entry를 build하고, handler에 return한다.

     

     


     

     

    여기까지, forground일때 complication을 업데이트하는 방법을알아봤다.

     

    하지만, 굉장히 자주 우리앱은 포그라운드에있지않는다. 

     

    이런경우에는 Bacgrkound App Refresh와 같은 메커니즘을 이용하여 complicaiton을 업데이트해야한다.

     

    앱은  Background App Refresh를 한시간마다 4번까지 사용할수있다.

    watch face에 얼마나 자신의 앱complication있든 상관없다.

    그리고 실제 기회횟수는 다른프로세스들이 얼마나많이실행하고있고, 배터리사용량에따라 결정된다. 

     

    하는 방법은, WKExtensionscheduleBackgroundRefresh API를 이용한다.

    이를통해, background에서 앱이 실행되도록 하게한다. 

     

    아래 코드는 scheduleBAR는 backgroundAppRefresh하는 방법을 작성한 코드다. 

     

    .addingTimeInterval()에 들어가는 파라미터는 1초를의미하고,

    현재시간으로부터 + alpha 시간이후부터 background App refresh를 하도록 의미한다.

    ( 즉, 아래코드는, 처음시작이면, 60초, 그이후부터는 15분이후! ) 

    당연히 시스템은 여러조건들을고려하여 이것을 실행하기때문에, 반드시 그시간이 보장되는것이아니며, 최소시간이다. 

    일반적으로 1~2분이내에 실행이되겠지만, 시스템조건에따라 결정된다. 

     

    dictionary를 이용하여 데이터를 넣어준다.  ( 이예제는 그냥 어떻게 진행되는지보기위해, 현재시간을넣었다. ) 

     

    마지막으로 scheduleBackgroundRefresh API를 호출하면, 메인스레드에서 비동기적으로 호출된다. 

    제출이되고,  앱이 active상태가되고나면,  해당 작업을 처리할 메서드( handle ) 를 호출하게된다. 

     

    handle을 호출하는과정에있어서, 실질적으로 complication을 업데이트를 호출한다. 

    그리고, 그다음의 background App refresh를 호출한다. 

     

     

     

    handle하는 코드를 살펴보자.

    이 코드는 Xcode가 extension delegate코드를 생성할때, 같이 handle 메서드도 작성해준다. 

    ( 일반적으로 모든 작업종류에대해 생성해줌 ) 

    시스템은 처리해야할 작업들이여러개있을수있으므로, 분기처리한다. 

     

    backgroundTask일경우에, 

    앞서넣었던 userinfor를 가져오는데,

    구현과정을 보기위해서, 제출된시각으로부터 얼마나 시간이지났는지를 Print한다. 

    그다음 실질적으로 complication update를 호출하고,

    다음번을위한, 다시 backgroundAppRefresh를호출한다. 

    그리고 작업을다 처리완료했다는 의미로, setTaskCompletedWithSnapshot을 호출하고,

    false를 넣은이유는 Snapshot을 필요하지않기때문이다. 

    background task를 완료하면 앱은 suspended상태로될텐데, 그러므로, 그전에 미리 필요한다른작업들을 해놓아야한다. 

     

     

    만약 이과정에서, HealthKit으로 접근하여 데이터를 가져오기위해서는 어떻게해야할까? 

     

    따로 healthDataProvider를 만들어두어, 

    refresh를 비동기적으로 호출하여, 똑같이수행하면된다.

     

     

     

     

    요약하면, 

    Background App Refresh는 한시간에 최대4번만되고,

    오직 한 요청만할수있으니, 연속적으로하기위해서는 위와같이, 한번요청하고, 그다음 요청, 이렇게연쇄적으로 하라. ( 마치 재귀함수처럼 ㅎ ) 

    중요한점은 네트워킹은 안된다.  Watch에서 사용가능한 API들은 가능하지만, URLSession을 이용할경우는 error와함께 fail된다!

    앱은 active CPU time은 최대 4초이다. 

    • 그러므로, 많은처리를할경우, 작게나누는방법을고려하라. 

    앱은 처리하는 총 시간은 최대 15초다. 

     


    여기까지 background app refresh를 알아봤는데, 

     

    백그라운드에서 네트워킹이필요한 데이터가 있을수도있다. 

    그런경우에는 Background URLSession을 사용한다. 

    • 앱이 running하고있지않아도 스케쥴링및 데이터를 받을수있도록해준다. 
    • 마찬가지로 한시간에 4번요청및 받을수있고, WI-FI나,셀룰러, 베터리수명에따라 결정된다. 

    Your app can have multiple outstanding background download tasks. 

    Always make sure to attach to your session when your app is launched so you can receive the URLSession delegate callbacks.

     

    WeatherDataProvider는 URLSession Delegate를 채택했고, 

    background urlsession configuration을 생성하고, 

    sessionSendsLaunchEvents 를 true로함에따라, 백그라운드에서도 앱을 깨우도록한다. 

     

     

     

    만들어진 configuration을 통해, download task의 urlsession을 생성하고, 

    earliest begin date를 설정한다.  

    • 스케줄링될 시간을 의미한다.

    그리고 작업을 resume하여 시작한다. 

     

     

     

    URLSessionDownloadDelegate를 채택하는 WeatherDataProvider 클래스다. 

    background configuration으로만들고,

    이 configuration을 이용하여 URLSession을 만든다.

    delegateQueue를 nil로 두면, URLSesison의 반응들은 background serial queue로 보내진다.

     

    계속해서 위에만들어두었던 backgroundURLSession으로 , download task를 만들고 스케줄링하는 메서드를 작성했다.

     

    다운로드가 완료되면 WKExtension delegate로 WKURLSessionRefreshBackgroundTask를 받게된다. 

    그러므로, 이를 처리할 메소드를 사용해주는데, WeatherDataProvider클래스를 이용할것이다. 

     

    reuqest가 성공적으로 끝났다면, delegate는 didFinishDownloadingTo 를 호출하게된다.

    성공적이든 아니든, delegate는 didCompleteWithError를 호출한다. 

     

     

    네트워킹이 다 완료됐다면, WeatherDataProvider는 completion handler에 받은데이터를 넣어주고,

    이를통해 complication을 업데이트할것이다. 

     

    ExtensionDelegaet에 호출되는 handle 메서드에서, 

    WKURLSessionRefreshBackgroundTask를 분기하여, 처리한다. 

     

     

    weatherDataProvider 의 refresh()는 다음과같이구현되는데,

    ExtensionDelegate -handle에서 작성한 클로저를 저장한다. ( !!! ) 

     

     

     

    URLSessionDelegate  - didfinishDownloadingTo 에서 데이터를 받아오면, 데이터처리를해주고, 

     

     

    didCompleteWithError 에서는 에러가없다면 아까 저장한 클로저를 호출해준다. 

     

     

    ( 허허... URLSesssionBackgroundTask는 좀 복잡하네요 ) 

     

     

     

    추가적으로,  request가 깔끔한경우가 아닐때가 있다 

    예를들어, url을 업데이트해야한다거나, 어떠한 이유로 download task를 취소해야하는경우나 인증문제처럼.

     

    우선 이전과같이, Extension delegate를 통해서 handle메서드가 호출된다. 

     

     

    그리고 WeatherDataProvider를 이용하여 request를 핸들링한다.

     

    willBeginDelayedRequest는 앱이 URL request를 업데이트하거나 캔슬할 수 있도록 해준다.

    예를들어 request를 만든ㄴ지 꽤 시간이 지난경우처럼, 

     

    그리고 didReceive challenge에서는 권한문제들을 처리하도록 하게해준다. 

     

    didFinishEvents는 모든 이벤트들이 전달되어졌을때 받게되고, 이때 completion handler를 호출한다.

     

     

     


    마지막으로 Complications push가 남았다.

    때에 따라 서버로부터 데이터를 가져오는 것보다 더 효과적일 수 있다.

    서버는 각 watch마다 하루에 50개의 complication push를 보낼 수 있다.

     

    complication push를 설정하는 방법은,

    app bundle id를 포함하고, .watchkitapp.complication 을 포함하는 identifier가 필요하다.

    매우 중요하다. 만약 포맷이 지켜지지않으면, 푸시가 애플서버로부터 거절당하거나, watch에 전달되지않을 수 있다.

     

    만든 identifier로,  Apple push notification SSL Certificate를 만든다. 

    그리고 프로젝트에서도 background modes - remote notification을 체크한다.

     

     

    그리고 앱에서는, PushNotificationProvider를 이용하여 PushKit과 함께 regsiter한다.

    그리고 성공적이라면, 앱은 credential을 받게된다.

     

    그럼 그받은 credential로 서버에 업로드하고, 서버는 watch와 커뮤니케이션이 가능해진다.

     

     

    PKPushRegistry을 생성하고, 콜백을 받을 큐를 정한다.

    .complciation을 정함으로써, 아까 만들었던 identifier와 매치되도록 한다.

    registration이 정상적이라면, 아래가호출되어 credential을 받게되고, 서버에 보낸다. 

     

    이렇게 서버에게 업로드되면, 

    서버는 필요할때, 해당 credential을 이용하여 Apple server에 푸시한다. 그럼 애플서버는 watch에게 전달한다. 

     

     

    complicaton push를 보낼때는, 포멧을 아래와같이 유지해야한다. ( aps - content-available ) 

     

    푸시가 watch에 전달될때는,  didReceiveIncomingPushWith가 호출되고, 

    그안에서 모든 처리가 완료된후에 completion 핸들러를 호출하도록 한다.

     

     

    푸시가 잘됐다면, 앱은 다시시작되고, 런치되어진다. 

    그때가 지정했던 큐로 아래가 호출된다.

    위에서 말했던것처럼 처리를한후에 completion을 호출한다.

     

     

     

     

     


     

    전체적으로 요약하면 다음과같이 표로 나타낼 수 있다.

    댓글