Home [iOS] Daily Football 출시 회고
Post
Cancel

[iOS] Daily Football 출시 회고

SeSAC iOS 과정을 진행하며 약 3주 간의 스프린트를 거쳐 앱 스토어에 앱을 출시하였습니다. 첫 번째로 출시한 앱인만큼 감회가 무척 새로울 것 같았지만, ‘진짜 이제 시작이구나’ 라는 생각과 동시에 제 스스로의 역량에 부족함을 많이 느끼게 되는 계기가 되었습니다. 기획부터 출시까지의 경험을 회고하며 부족했던 점들을 개선하고자 이 글을 작성하였습니다.

기획

어떤 앱을 만들 것인가라는 고민은 생각보다 쉽지 않았다. 개발자는 단순히 화면을 만들고, 기능 개발에 급급하기보단 서비스의 가치에 중점을 두어야한다고 생각한다. 내가 만드는 앱이 사용자에게 어떤 가치를 제공하는지 사용자 측면에서 고려하는 것이 중요하다고 판단했다. 그러기 위해선 개발자가 서비스의 도메인 지식에 보다 친숙해야한다. 평소 내가 어떠한 주제에 관심이 많은지 생각해보았고, 최근 해외 축구 경기 소식을 빼먹지 않고 챙겨보던 것이 생각이 났다. 축구에 관심이 많은 내가 열정적으로 고민하고 개발할 수 있는 앱이 무엇일까? 고민해보니 다음과 같은 핵심 기능들을 추려볼 수 있었다.

핵심 기능

  • 전세계(?) 리그 데이터
  • 리그 구단 순위
  • 리그 선수 득점/도움 순위
  • 리그 경기 일정
  • 경기 결과 및 세부 통계

그 외에도 경기 일정에 맞춰 사용자에게 푸쉬 알림을 보낸다거나, 경기가 진행되는 동안 실시간으로 경기 데이터를 제공하는 등 여러 아이디어가 생각났지만, 정해진 스프린트 기간 내 출시하는 것이 목표다보니 출시 후 업데이트 기능으로 분류하였다.

자료조사

기획 단계에서 가장 우려스러웠던 부분은 앱 아이디어 특성 상 모든 콘텐츠를 제 3자로부터 제공 받아야 한다는 것이었다. 축구 리그 데이터를 제공하는 API들이 여럿 있었지만, 대부분 해외에서 운영되는 유료 API였고, API마다 일일 rate limit과 제공하는 데이터 Coverage가 상이했다. 이러한 API에 상당히 의존적인 앱이다보니 사용해보지도 않은 API들을 쉽사리 선택하기 어려웠다.

대표적인 위 2가지 API 중에서 하루 100회 무료 호출이 가능한 API-Football을 선택하였다.

개발

UI 구현

1. Expandable Cell

리그 데이터를 테이블 뷰에 표시할 때 국가 단위로 그룹핑하여 국가 셀을 펼쳤을 때 리그 셀이 나타나도록 구현하였다. TableView DiffableDatasource를 사용하여 셀이 펼쳐지고 닫히는 동작을 손 쉽게 구현할 수 있었다. DiffableDatasource의 SnapShot 방식 특성 상 Item 모델이 Hashable을 준수해야하는데 셀의 펼치짐/닫힘 상태를 DiffableDatasource가 구분할 수 있어야 Snapshot이 Apply되기 때문에 뷰의 상태에 관한 프로퍼티를 모델이 지녀야 하는가? 에 대한 의문이 들었지만, 더 나은 해답을 찾지 못한 아쉬움이 있다.

2. sticky Header with Nested Scroll

리그 상세 데이터를 효과적으로 프레젠트할 수 잇는 Tab menu UI를 채택하였는데, 특정 스크롤 시점부터 Tab bar가 화면 상단에 자연스럽게 고정되도록 구현하였다. Tam Menu UI는 Tamman 라이브러리를 사용하여 구현하였다보니 구조 상 스크롤 뷰가 중첩되는 상황이 발생하였다. OuterScroll과 InnerScroll의 스크롤 전환을 자연스럽게 구현하기 위해 두 스크롤 뷰의 기본 스크롤을 비활성화하고 OuterScroll에 UIPanGestureRecognizer를 직접 구현하여 사용자의 팬 제스처에 따라 OuterScroll, InnerScroll의 Offset을 직접 조정하도록 하여 Nested Scroll을 성공적으로 구현하였다. 이 과정에서 상속과 델리게이트 패턴, 메모리 해제 등 여러 트러블 슈팅을 겪을 수 있었다. 구체적인 내용은 Github README에 기술하였다.

아키텍처 패턴

MVVM 구조를 도입하였다. 기존의 MVC 구조에서 컨트롤러가 담당하던 뷰와 모델 사이의 비즈니스 로직들을 덜어내 뷰 모델이라는 레이어를 하나 더 둠으로써 뷰 컨트롤러는 오로지 프레젠팅과 사용자 상호작용과 관한 로직만을 담당하게 하였다. 기획 단계에서 우려되었던 API 의존도를 나름 해결하기 위한 사후 대책으로 아래와 같이 UseCase와 Repository, Router 패턴을 사용하였다.

  • 네트워크 관련 로직들은 Router 패턴으로 한층 추상화된 수준으로 관리, 향후 다른 API로 교체하거나 추가될 경우를 대비하였음
  • API 호출 및 로컬 Realm 관련 로직은 레포지토리로 분리하였음
  • API, DB 의존 모델들을 뷰 계층이 요구하는 데이터 유형에 맞게 처리하여 반환하는 UseCase 레이어를 두었음 궁극적으로 뷰모델이 지니는 비즈니스 로직에서 API와 DB에 관련된 로직을 최대한 분리하였다. 이 과정에서 뷰 모델이 레포지토리를 바로 사용해도 될법한데? 싶은 상황에서도 UseCase 레이어를 하나 더 두는게 합리적인지 의문이 들었다. 또, 의존성 주입이 불가피하게 발생하는 부분을 겪게 되어 의존성 주입을 어떻게 해결할 수 있는지 공부하게 되는 계기가 됐다.

아쉬운 점

콜백 지옥

API와 Realm 관련 메소드들을 전부 escaping 비동기 클로저 방식으로 구현하여 콜백 지옥을 겪게 되었다. Swift Concurency에서 도입된 async await 키워드를 통해 해결할 수 있지만, 이 개념을 적용하기엔 개발 단계가 상당히 진행된 상태였다. async await을 도입하려면 결국 비동기 관련 로직이 전부 async await을 사용해야하기 때문에 추후 리팩토링할 예정이다.

출시

API가 전세계 축구 리그 데이터를 제공하다보니 다국어 대응을 통해 앱스토어 등록 역시 영어와 한국어 모두 준비해서 다국어로 심사를 제출했다. 리젝은 많이 경험해볼 수록 좋을 것 같다는 생각이 들었는데, 앱 내 기능이 적어서 그런지 1번 밖에 겪지 못했다. 사유는 Guide line 4.1 Copy cat

API가 제공하는 클럽 팀 로고, 리그 로고 등이 앱 내 컨텐츠와 앱 스토어 메타데이터(스크린샷)에 포함되는 것이 주된 문제였다. 이를 해결하려면 API가 제공하는 이미지에 대한 라이센스를 내가 보유하고 있는지 소명해야했다. 일개 취준생 개인이 이걸 소명하는 것은 불가능에 가깝다고 판단해서 빠른 출시를 위해 스크린샷와 앱 내 UI를 즉각 수정하여 심사를 다시 제출했고, 곧 바로 통과되었다.

Daily Football - App Store

SeSAC iOS 과정을 진행하며 앱 스토어 출시와 유지보수라는 귀중한 경험을 할 수 있어서 감사하다.

This post is licensed under CC BY 4.0 by the author.