안녕하세요. 한끼족보의 iOS 파트를 담당하게 된 심서현입니다.
저는 한끼족보를 통해, iOS 개발을 처음 도전하게 되었는데요... '한끼족보'를 만들어가는 과정 속 개발에서 웹과 앱의 차이를을 바탕으로 알게되었던 많은 것들을 차례대로 이야기해보려고합니다.
사용자 관점에서 웹과 앱의 차이
요즘 시대에 웹과 앱을 안 써보신 분은 없을 거라고 생각됩니다. 가장 명확한 차이는 접근성이라고 생각됩니다.
웹은 웹 브라우저만 있다면 어떤 디바이스에서든지 접근할 수 있는 반면, 앱은 디바이스에 설치하는 과정이 필요합니다. 이 과정 속에서 오프라인 접근성도 다시 확인 해 볼 수 있겠죠. (한끼족보는 온라인 온리이긴 합니다) 더하여 업데이트 및 유지 보수 과정에서 크고 작은 차이가 있습니다.
얼핏 보면, 사용자의 관점에서는 모바일 웹과 모바일 앱의 차이를 크게 느끼지 못 할 수도 있다고 생각됩니다. 저같은 경우에도 네이버 블로그 앱을 사용했다가 블로그 모바일 웹과의 큰 차이를 모르겠어 삭제했다 깔았다가를 반복하고 있습니다.
하지만... 개발에서는... 너무나도 달랐다는 것을...
개발자 관점에서 웹과 앱의 차이
웹은 react TS, 앱은 Swift UIKit 을 기반으로 작성되었습니다.
기본적으로 Swift 의 UIKit은 명령형, React는 선언형의 언어를 사용합니다. 여기서부터 아주 큰 애를 먹었는데, 추가하여 상태관리 툴인 RxSwift, useState의 개념을 이해하는데에도 꽤 걸렸습니다.
같은 클라이언트라 기존 지식을 바탕으로 개발을 할 수 있을 것이라 생각했지만, 웹에서 고려애야하는 부분과 앱에서 고려해야하는 부분이 달랐습니다. (물론 기존 지식이 도움이 안 되었다는 것은 아닙니다. 다만... 개발시에 가중치를 다르게 두어야해서 조금 어려웠던 것 같아요.)
그 중 이번에는 view의 관리와 stack 관리에 대해 서술해보고자합니다.
History Stack VS Navigation Stack
웹은 보통 SPA 방식으로 구현 됩니다.
Link나 useNavigate로 페이지 이동을 관리하고 새로운 URL 을 기반으로 브라우저의 history stack에 이동한 경로가 쌓이게 됩니다. 이때 Router를 통해 URL 경로에 따라 알맞은 컴포넌트를 랜더링합니다. useParams로 URL 파라미터를 가져와 세부 페이지를 렌더링을 진행할 수 있습니다.
웹의 경우는, 컴포넌트가 unmount 됐을 때 관련 된 상태와 리소스를 자동으로 정리하게 됩니다. 즉 페이지를 이동하면 이전 페이지의 컴포넌트들이 unmount 되고, 관련된 메모리를 정리합니다. 이후 새로운 페이지의 컴포넌트를 mount 하게 됩니다.
즉 웹 개발에서는 비동기 작업이나, 이벤트 작업 등 예외의 경우를 제외하고는 페이지 이동시 메모리 관리에서 비교적 자유롭습니다.
반면, 앱(iOS)개발의 경우 navigation stack으로 화면을 전환합니다. 얼핏 비슷해보이지만, 아주 큰 차이점이 있습니다.
Swift의 Navigation 에서는 이동하고자 하는 view에 해당하는 view controller객체를 생성한 뒤, navigation에 그 view를 push 하는 방식으로 화면이 전환됩니다. 해당 view에서 다른 view로 이동하려면, 이동할 view controller 객체를 생성된 뒤, 그 위에 view를 push 하는 거죠.
즉, 이 방식에서는 별도의 처리를 하지 않으면 이동한 만큼 view controller가 navigation stack에 쌓이게 됩니다. 결국 view controller 와 관련된 리소스 메모리도 남아 있게 되며, 이로 인해 메모리 누수가 발생할 수 있습니다. 결국 성능이 저하되는거죠.
결국 앱 개발에서는 push 외에도 pop을 통해 navigation stack을 지속적으로 관리하며 메모리를 확보해야합니다.
기획에서 이런 플로우를 요구했습니다.
메인 페이지 -> 마이페이지 -> 나의 족보리스트 -> 나의 족보 내 식당리스트 -> 다시 메인페이지
어떻게 개발해야할까요?
웹 개발의 경우
웹 개발의 경우 URL을 통해 끊임없이 history stack에 URL을 쌓는 방식으로 설계하는 것이 보편적입니다.
앱 개발의 경우
앱 개발의 경우, 웹과 유사한 방식으로 계속 navigation stack을 쌓았다가는 메모리 누수가 발생합니다.
ui에 back button이 없는 메인 뷰로 이동하는 경우이기 때문에, 이전 뷰로 가는 일이 없다고 판단했습니다. 즉, 메인 뷰로 이동하기 위해서 popToRootViewController 명령어를 통해 navigation stack을 깔끔하게 밀어줍니다.
상태 동기화에 관하여
navigation stack에서 문제사항은 하나가 더 있습니다. 이미 생성되어 navigation stack에 쌓여있는 view controller 간의 상태 동기화에 어려움이 있다는 점입니다.
웹에서 서버와 통신하여 데이터를 가져오는 경우에는 useEffect, fetch, react-query 등 다양한 라이브러리를 바탕으로 페이지가 다시 로드 될 때 마다 서버에서 데이터를 가져와 최신 데이터를 반영할 수 있습니다. 더불어 context, redux, recoil, jotai 등 다양한 상태관리 라이브러리로 상태를 전역적으로 관리할 수 있으며, 즉각적으로 반영할 수 있습니다
반면 앱에서는 어떨까요? 앞서 기술한 것과 같이 이미 생성되어 navigation stack에 쌓여있는 view controller는 이미 생성 되어있으며, 페이지 이동시 (뒤로 가는 경우) 새로 로드되는 것이 아닌, 기존에 생성되어 있던 view controller 가 다시 보여지는 것이기 때문에 상태가 자동으로 동기화 되지 않습니다. 즉, 페이지 이동시 상태 동기화를 위해 따로 처리해줘야하는 과정이 필요합니다.
이번에는 이런 플로우를 구현해보고자 합니다.
1. 메인 화면에서, 식당의 세부 정보를 조회하기
2. 식당의 정보가 잘못 된 것 같아, 식당을 신고하기 -> DB에서 해당 식당이 사라집니다.
3. 메인 화면으로 돌아가기 (이때 신고한 식당은 식당 리스트에서 제거되어야함)
웹 개발의 경우
식당 세부 정보 페이지에서 메인 페이지로 이동 되는 순간, 페이지가 다시로드 됩니다. 페이지에 내장 되어있는 식당 리스트를 불러오는 API가 다시 호출되어 식당 리스트에서 신고한 식당이 사라진 것이 즉각적으로 반영됩니다.
앱 개발의 경우
기존에 생성되어 있던 view controller 가 다시 보여져서, 식당 리스트에 신고된 식당이 여전히 존재합니다.
저는 해결 방법으로 view controller life cycle을 활용했습니다. view controller가 다시 화면에 표시 되기 직전(viewWillAppear), 식당 리스트를 불러오는 API를 한번 더 호출해 식당 리스트를 업데이트 하는 로직을 작성했습니다.
식당 리스트가 업데이트 되면, 업데이트 된 데이터를 바탕으로 collection view의 data bind 또한 다시 진행해주었습니다.
웹 개발만 했던 사람의 iOS 도전기 1을 마무리하며...
간단하게 웹과 앱의 history stack과 navigation stack으로 발생하는 차이점과 문제들, 그리고 해결방법을 알아보았습니다. 웹과 앱 개발은 뿌리는 같은 듯 다른 부분이 정말 많다는 생각을 하며...
더 좋은 글로 돌아오겠습니다.
https://github.com/Team-Hankki/hankki-iOS
GitHub - Team-Hankki/hankki-iOS: 한끼줍쇼아니라고52817번말했다
한끼줍쇼아니라고52817번말했다. Contribute to Team-Hankki/hankki-iOS development by creating an account on GitHub.
github.com