NSScreencast iOS 앱에서 비디오를 다운받아  오프라인에서도 사용할 수 있는 기능을 넣고 싶었다. 보통 비디오는 80에서 200MB 크기인데, 이것에는 실패시 다시 복구하는 다운로드 시스템을 만들기 위해 신경을 조금 쓸 필요가 있었다.



첫번재로 해야 할 일은 에피소드 화면에 진행상태가 보이는 다운로드 버튼을 넣는 것이었다. 여기서는 iTunes에서 음악을 다운 받는 것과 비슷하게 구현했다. 다운로드는 백그라운드에서 일어나며 진행 노티피케이션을 받아서 UI에 퍼센테이지로 나타낸다.




NSOperation 서브클래스를 통해 실제 다운로드가 끝난다. 이런 방식은 동시성을 제어하고, quality of service과 비행기 모드 다운로드에서 취소시키는 메커니즘을 쉽게 구현할 수 있게 해준다. 다운로드 진행은 에피소드 id를 가진 노티피케이션을 통해 보내지는데, 이 부분과 관련된 UI는 이것을 가져와서 갱신할 수 있다.


물론 다운로드 되는 동안 사용자가 화면을 응시하고 있을 필요는 없으므로, 앱을 둘러보든 기다리든 다운로드는 계속 진행될 것이다.

다은으로 어떤것이 다운중인지, 다운받은 비디오를 오프라인에서 보고, 저장공간을 확보하기 위해 다운로드 중인 것과 예전에 다운로드 받은 것들을 한 곳에서 볼 수 있게 하고 싶었다.

이 화면은 각 줄이 에피소드로서 테이블 뷰 안에 데이터를 보여준다. 현재 다운로드 받은 셀들은 진행 노티피케이션을 받아서 빨리 다시 불러와야 한다.

이 모든것들을 하기 위해 상태를 코어데이터 모델로 저장했다. 나의 DownloadInfo 모델은 아래와 같이 생겼다.


이것을 위해 코어데이터를 활용함으로서 그 라이프 사이클 내내 다운로드 상태를 추적할 수 있다. 이전에는 plist를 사용했었는데, 간단한 저장소로 plist보다 코어데이터가 더 간편했다.

모델에 다운로드 진행 상태 퍼센테이지를 저장하는 것을 볼 수 있지만, 이것을 반복적으로 코어데이터에 저장하지는 않았다. 빠른 커넥션에서 다운로드 진행 변화가 빠르게 일어날 것이고, 코어데이터를 매번 저장할 필요는 없었다. 요청이 취소되거나 다운로드가 더이상 일어나지 않음을 UI에 보여주고 싶을 때만 데이터를 저장했다.

코어데이터를 사용해서 얻은 또다른 장점에는 빠르게 DownloadsViewController를 구성하기위해 NSFetchedResultsController의 유용한 점을 사용할 수 있다는 것이다.

실패 처리하기
네트워크는 항상 뭔가 잘못될지도 모른다는 가능성을 가지고 있다. 이러한 가능성은 큰 파일 다운로드시 더 커진다. 사람들이 Wi-Fi 존을 벗어나거나, 터널로 들어간다던지, 비행기 모드를 켠다던지, 다른 다운로드들과 함께 한꺼번에 많이 다운받는다던지 할 수도 있다. 최고로 좋은 사용자 경험을 보장하기 위해서는 이것을 다루고 싶었고 사용자가 빨리 재시도를 할 수 있도록(때때로는 자동으로 재시도 하도록) 하고 싶었다.

실패가 일어나면 Download의 state 프로퍼티를 .failed로 바꾸고 UI를 적절하게 갱신한다. 그 셀을 다시 불러오고 사용자는 다운로드를 재시도 하기위해 탭할 수 있다.

일시정지와 재개
NSURLSession API가 시작되면, 요청을 취소하고 resume data라 불리는 애매한 오브젝트를 만들어내는 기능을 추가한다. 이것을 이용하면 끊긴 곳에서 요청을 시작할 수 있으며, 한가지 의문은 나중에 다시 시작할 수 있도록이 데이터를 유지하는 방법이다. NSScreencast 모델에 추가하는 것이 딱 알맞았다. 사용자가 진행중에 다운로드를 누르면 downloadTask.cancel(byProducing:)을 호출하고 나중에 사용하기위한 재개 데이터를 모델에 저장한다.

다운로드가 시작되고 모델 데이터가 재개 데이터라면 어느 시점에서 끊겼든지 그 곳에서 요청을 재개하는데 사용된다. 이 기능은 추가하기 쉬운데다, 큰 파일 다운로드시 굉장히 유용하다.


셀룰러 다루기
나는 다른 사람들의 데이터 요금을 불태우고 싶지 않았으므로 NSURLSessionConfigurationallowsCelluarAccessfalse로 설정해두었다. 그릭고 사용자가 원할때 셀룰러로 다운받을 수 있게 토글을 추가했다.




Fx Reachability를 사용하여 커넥션 상태를 모니터링했다. 사용자가 셀룰러일때 에피소드를 다운받으려 한다면 토글을 띄워 설정하고 다운로드 할 수 있게 하였다.

모델과 파일시스템의 동기화 유지하기(Keeping the Model and FileSystem in Sync)
파일에대한 메타데이터는 디스크에 저장하기 때문에 이것이 항상 동기화 되어있음을 보장해야한다. 에피소드를 하나 삭제하면 코어데이터 모델 뿐만 아니라 디스크에 파일도 삭제해주어야한다. 이 동기화를 보장해주기위해 나는 CleanupDownloadsOperation을 가지고 있는데, 이것은 앱이 켜질때 실행되며, 각 저장된 DownloadInfo가 디스크에 옳바른 파일을 가지고 있는지(혹은 삭제되었는지) 확인하고, 다운로드 폴더의 각 파일이 코어데이터에 저장되 있는지(혹은 삭제되었는지) 확인한다.

이렇게하여 뭔가 잘못되거나 두가지 상태(데이터페이스/디스크)가 싱크가 맞지 않을때의 대비책을 마련해 둔 것이다.

백그라운드 다운로드
겉으로 보기엔 간단해 보이지만 백그라운드 다운로드는 혼란과 복잡함의 근원으로 표현된다. 이 주제에 대해서는 나의 다음 포스팅에서 다루겠다.

그들이 말하는 그냥 오프라인 다운로드 추가하기
처음에 오프라인 다운로드를 앱에 넣어보기 전까지는 그냥 하루 이틀정도 더 걸리는 작업이라 생각했지만, 어마무시하게 복잡한 작업이 되버렸다.

역시 이런것이 소프트웨어라 생각된다.




WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,