1. 메뉴얼은 어떤 구조를 가져야할까
메뉴얼은 RIBs 아키텍쳐를 도입한 첫 번째 사이드프로젝트였기 때문에,
가장 오랜 시간 고민을 했던 것은 "어떻게 구조를 짜야하는가?" 였습니다.
기존에 진행했던 ViewController-ViewModel을 설계하는 것과는 사뭇 다른 개념이었기 때문에,
독립적이고 기능 단위로 만들기 위해서 더욱 노력했습니다.
또, '게시글 작성'과 '게시글 수정'과 같은 비슷한 기능을 하는 것은 하나의 RIB으로 재활용하고 싶었습니다.
RIBs를 도입하기 전에, 이 아키텍쳐를 어떻게 활용하면 가장 효율적이게 사용할 수 있을지 고민했던 것 같습니다.
1-1. 기능 단위로 분류해보자
메뉴얼을 작성하고 확인하기 위해서 어떤 RIB이 필요할 지, 먼저 큰 기능 단위로 분류하고자 했습니다.
어떤 스크린인지 정의하고 해당 스크린이 어떤 기능을 제공해야 하는지 상세하게 작성했습니다.
RIB | 정의 | 기능 |
DiaryHome | 메뉴얼 진입시 가장 먼저 보이는 스크린 | - 일기를 리스트로 나타내준다 - 추천 일기를 리스트로 나타태준다 |
DiaryWriting | 메뉴얼 작성 스크린 | - 메뉴얼을 작성할 수 있는 텍스트필드 제공 - 임시저장한 메뉴얼을 불러올 수 있도록 한다 - 사진을 첨부할 수 있도록 한다 - 메뉴얼 작성/ 수정 기능 제공 |
DiarySearch | 메뉴얼 검색 스크린 | - 메뉴얼 검색 기능 제공 - 최근 검색한 메뉴얼 리스트로 제공 - 검색한 메뉴얼의 상세 스크린으로 이동할 수 있도록 제공 |
DiaryDetail | 메뉴얼 상세 스크린 | - 작성한 일기 상세 내용 확인 기능 제공 - 겹쓰기 기능 제공 - 일기 삭제 및 알림기능 제공 |
1-2. 재활용 가능한 RIB이 있는가
여기서 제가 집중했던 것은 재활용 가능한 RIB이 있는지 확인하는 것이었습니다.
이전에 제작했던 <디모다모> 같은 경우에는 '글 작성' 과 '글 수정'이 다른 VIewController로 이루어져 있었습니다.
글 상세 페이지가 변경되면 글 작성하는 UI도 함께 변경되어야 하는 번거로움이 있었는데요.
특히 이번 메뉴얼은 글 작성 스크린과 글 상세 스크린의 UI가 매우 비슷했습니다.
상단에 임시저장 정도의 차이를 제외하면 거의 같다고도 할 수 있었습니다.
때문에 Interactor 설계 단계부터 수정모드와 작성모드를 염두하여 개발을 진행하게 되었습니다.
만약 깊은 고민 없이, 이전 프로젝트 <디모다모>처럼 분리해서 진행 했다면, 약간의 편리함(?)은 얻을 수 있어도
결국 중복코드가 생기게 되고 유지보수의 어려움이 따라오게 되었을 것입니다.
1-3. RIB 구성에 필요한 데이터는 무엇인가
Builder에 Dependency를 설정하고, 이 Dependency를 바탕으로 Interactor가 조리(?)를 할 수 있기 때문에
가장 중요하게 생각했던 것 중 하나가 "어떤 데이터가 필요한가?" 였습니다.
설령, 게시글 수정할 때 DiaryDetail -> DiaryWriting 으로 스크린이 이동할 때,
"부모 RIB인 DiaryDetail한테 DiaryWriting은 무엇을 요구해야할까?" 라고 고민했을 때..
- 메뉴얼의 타이틀, 내용, 일기 등...
- 수정하고자 하는 메뉴얼의 UUID
- .... 수도 없이 많은 메뉴얼을 이루고 있는 데이터들
복잡하지 않고 쉽고 빠르게 넘기기 위해서는 결국 DataModel을 효율적으로 구성하는 것이 중요했습니다.
여러 파라미터를 넘기고 하나하나 세팅하기에는 일기에 구성되는 정보들이 너무 많았거든요.
RIB만 고민하고 있었는데, 결국 일기를 이루고 있는 데이터는 무엇인가? 까지 생각을 확장해야 했습니다.
하지만, 일단 RIB을 어떻게 구성할지 고민하고 있었기 때문에 데이터 모델은 효율적으로 구성되었다는 가정(!)하에,
어떤 정보를 넘겨 받으면 좋을지 깡통 데이터 모델을 만들어서 프로토타입을 제작했습니다.
2. 메뉴얼은 어떤 구조를 가졌을까
일기장 어플리케이션은 단순히 '홈-작성-보기'만 있으면 되지 않을까?
따로 엄청난 기능이 없기때문에 할만하지 않을까? 라고 생각하고 작업을 진행했습니다만,
막상 진행하고 보니 서로 유기적으로 엮여있는 스크린이 굉장히 많았습니다.
이런 상황에서 독립적으로 기능 단위로 만든 메뉴얼의 구조가 장점이 되었던 것 같습니다.
하지만 거의 모든 RIB에서 사용되었던 BottomSheet은 더욱 비대해지는 문제도 겪게 되었습니다.
3. RIBs를 사용한 후기
RiBs를 사용해보니 기능 단위 그리고 모듈 단위로 개발하기 매우 적합한 아키텍쳐라는 것을 느꼈습니다.
의존성 역전을 활용해서 테스트 코드 작성이 매우 용이했고,
ViewController(Presenter)와 로직 코드(Interactor)와의 분리가 매우 깔끔하게 되었다고 느꼈습니다.
다만 Dependency가 점점 비대해져, 다양한 RIB에서 컨트롤하기 힘들다는 것도 느끼게 되었는데요.
어떤 부분에서 그렇게 느끼게 되었는지 아래 부분에서 조금 더 상세하게 작성 해보겠습니다.
3.1 기능 단위에 최고
일기 어플리케이션 특성상, 같은 뷰가 여러 곳에서 불려야하는 경우가 많았습니다.
아래의 경우 모두 DiaryDetail(일기 상세) 페이지로 이동하는 상황인데요.
- 일기를 검색하고 검색된 일기로 갈 때
- 최근 검색 리스트의 일기를 선택할 때
- 홈에서 상세 일기 화면으로 갈 때
이런 상황에서 RIBs는 매우 편리하게 연결할 수 있도록 도왔습니다.
이미 DiaryDetail의 Bulder에 Dependency를 설정 해놓았기 때문에,
다른 RIB에 연결할 때 미리 설정 해놓은 의존성을 놓치지 않고 체크할 수 있었습니다.
하지만 ViewModel을 사용했던 이전 프로젝트에 비해서, 부모 RIB과 자식 RIB 간 소통,
Presenter가 Interactor에 로직을 요청할 때 매번 Protocol에 함수를 정의해줘야 하는 것이 다소 번거로웠습니다.
하지만, 이 번거로움은 테스트 코드를 작성할 때 진가를 발휘했습니다. 👍
3.2 의존성의 비대화
메뉴얼은 사용자와의 +@ 소통을 대부분 BottomSheet을 활용했습니다.
새로운 스크린으로 넘어가서 소통하게 되면서 UX Depth가 늘어나는 것을 원치않았기 때문입니다.
BottomSheet을 RIB으로 만들어서 관리했는데,
메뉴얼 UX 특성상 거의 모든 스크린에서 불리게 되면서 Dependency 관리가 매우 힘들었습니다.
DiaryWriting에서 필요한 Dependency를 Builder에 설정해놓으면,
사실상 모든 RIB에서 BottomSheet의 의존성을 충족해야하기 때문에
모든 RIB에서 "해당 의존성을 만족해야 해!!!" 라고 소리치면서 빌드 오류가 나곤 했습니다.
그렇다고 Dependency에서는 제외하고, Router에서 직접 필요한 값을 주입하자니
builder나 interactor가 초기화 되면서 변수가 적절하게 초기화 되지 않으면서 타이밍 이슈가 발생하곤 했습니다.
(RxStream에 값을 넘겨줬는데 초기화 되기 전에 넘기게 되면서 데이터 전달이 되지 않는다든지..)
최대한 optional 값을 사용해서, 필요한 RIB에서만 값을 가져가도록 했는데 더 좋은 방법이 있을지 고민해야할 것 같습니다.
이 방법도 좋은 방법이 아닌 것이, 매번 옵셔널 바인딩을 활용해서 nil인지 아닌지 체크하는 상황이 연출되었는데,
과연 이게 맞는 방법인가 의문은 듭니다만.. 조금 더 공부해서 어떻게 Dependency를 관리해야 하는지 알아 보아야 할 것 같습니다.
3.3 메모리 관리에 경각심
메모리 관리에 대해서 깊은 생각 없이 개발을 해왔었습니다.
사실 메모리를 신경 쓸 겨를이 없는게 맞았던 것 같습니다.
새로운 기능을 개발하는 것도 쉽지 않고, 새로운 아키텍쳐와 기술을 습득하고 이걸 내 것으로 만드는 것도 어려웠습니다.
하지만 RIBs는 매번 런타임마다 제게 브레이크를 걸었습니다.
약간의 메모리 누수만 감지 되어도 RIBs는 앱을 강제 종료 시켜버렸습니다 (ㅋㅋㅋㅋ)
정확하게 어떤 이유인지도 알려주지도 않고, "이 RIB에서 메모리 누수가 발견됐는데 확인 해볼래?" 수준이었죠.
오히려 이런 상황이 제게 메모리를 조금 더 신경 쓰게 만들었던 것 같습니다.
weak var delegate: CustomDelegate? // 통과!
var delegate: CustomDelegate? // 런타임 오류
특히 'weak' 한 단어에 의해서 1주일동안 제대로 앱을 개발하지도 못했던 상황도 있었습니다. ㅎㅎ..
메모리에 대해서 많은 관심을 갖게 된 것도 나름 큰 수확..이라고 생각합니다.
모든 delegate나 observer를 사용하는 일이 있을 경우에,
deInit 또는 didDisAppear에 메모리 해제하는 습관이 들게 만들어 주었습니다.
4. 마무리
RIBs는 공부할 수록 참 어렵습니다.
정말 단순하게 버튼 하나를 누르는 액션에 로직 코드를 넣고자 할 때도, 개발자는 몇 가지 단계를 거쳐야만 할 수 있습니다.
A 스크린에서 B 스크린으로 Push 하고 싶을 때도,
두 RIB 사이에 의존성을 연결하고 Router가 연결 시켜주어야 하고,
Interactor와 Builder 사이에 전달 받은 변수를 초기화 해주어야 합니다.
사실 이전에 ViewModel을 '막' 쓸 때는 절대 필요 없는 과정이었어요.
그냥 이동하고자 하는 VC의 ViewModel을 직접 참조해서 값을 넣곤 했으니까요.
개발이 무작정 '편리' 하다고 '좋은' 것은 아닌 것 같습니다.
결국 개발은 혼자 하는 것이 아니라 다수가 하게 되는 것이고,
여러 사람이 하나의 스크린을 관리하게 된다면 제가 겪은 RIB의 '불편함'은 오히려 '명확함'으로 변화할 것 같습니다.
RIBs를 사용하면서 느낀 점은, "실제로 많은 개발자들이 사용하는 앱에는 어떻게 사용되고 있을까?"가 궁금해졌습니다.
여러 프로젝트를 보면서 견문을 조금 더 넓혀보고 싶다는 생각이 마구 드는 프로젝트였습니다.
4-1. RIBs 공부에 참고했던 자료
'Project > Menual' 카테고리의 다른 글
[iOS] 메뉴얼은 왜 이미지를 하나만 업로드할 수 있었을까? (0) | 2023.12.02 |
---|---|
[iOS] 유저에게 앱스토어 리뷰 요청을 해보자 (0) | 2023.04.22 |
[iOS] UX Writing을 보다 편하게 관리하기 위한 노력 (1) | 2023.03.05 |
[iOS] 출시 후 첫 신규기능 <온보딩> 업데이트 후기 (0) | 2023.02.12 |
[iOS] 일기장 어플리케이션 메뉴얼(Menual) 출시 회고 (0) | 2022.12.27 |