작성: 2026.1.26 전귀로
우리 프로젝트는 그동안 핵심 기능 구현을 최우선으로 하여 빠르게 달려왔습니다. 이 과정에서 필연적으로 코드 품질보다는 기능 동작에 집중하게 되었고, 현재 기술 부채가 일부 쌓여있는 상황입니다. 개발 중간에 구조 개선을 시도하려 했으나, 핵심 객체인 Advertiser와 Browser가 앱 전반에 깊게 관여하고 있어 중단한 바 있습니다. 당시에는 무리한 수정이 팀원들의 혼란을 야기하고, 기능 병합(Merge) 과정에서 치명적인 충돌을 일으킬 위험이 더 크다고 판단했기 때문입니다. 하지만 이제 기능 개발이 마무리 단계에 접어들었습니다. 남은 기간 동안 안정적인 서비스 배포와 향후 유지보수를 위해서는, 미뤄두었던 네트워크 레이어의 의존성을 분리하고 코드를 정리하는 작업이 필수적입니다. 이에 이번 리팩토링 주간을 통해 해당 부분을 집중적으로 개선하고자 합니다.
문제점
Store Protocol은 현재 함수를 action, reduce로 나누어 사용하고 있으나 reduce가 메인 스레드를 보장하지 않아 그 역할 구분이 명확하지 않습니다. 초기에 프로토콜을 정할 때 논의한 내용에 맞추어 action에서 일차적으로 intent에 맞춘 내용을 수행하고, 실제 state 업데이트는 reduce에서 이루어지고 있다는 점은 긍정적입니다.
하지만 reduce가 메인 스레드를 보장하는 것이 아니라 네트워크 레이어에서 흐름 제어를 할 때 항상 store 객체 밖에서 콜백을 DispatchQueue.main로 감싸 실행하고 있습니다. 이로 인해 뷰모델이 책임져야했을 UI 업데이트의 안전을 외부인 네트워크에서 책임지고 있습니다.
개선 제안
StoreProtocol send함수의 구현에 메인스레드 실행 보장을 추가합니다. 외부에서는 스레드 안전성 여부를 고려할 필요 없이 send를 호출하도록 수정합니다.
send 함수를 MainActor로 선언하는 방법이 고려됩니다.
문제점
Advertiser, Browser의 수많은 네트워크 분기 처리를 대부분 콜백으로 해결하고 있습니다. 현재 그 개수가 각각 10개 이상이 된 상태이며, 특정 콜백은 뷰의 생명주기와 함께 새롭게 할당하는 방식으로 재사용하고 있기 때문에 오류가 발생할 확률이 높고, 유지보수가 어렵습니다.
개선 제안
AsyncStream<PeerEvent>를 적용하여 로직을 개선합니다.
AsyncStream이란 기존의 델리게이트나 클로저 기반의 비동기 이벤트를 처리하기 위해 탄생한 도구로, 우리 코드에 포함되어 있던 PassthroughSubject와 유사한 역할을 수행합니다.
적용시 기존의 수많은 이벤트 처리 콜백을 하나의 AsyncStream 객체로 줄일 수 있고, 외부에서는 이를 for await 반복문으로 구독해 모든 이벤트를 처리할 수 있도록 합니다.
매번 콜백에서 weak을 사용해야 했던 문제점 또한 예방할 수 있어, 메모리 누수 방지의 장점 또한 예상됩니다.
문제점
Advertiser와 Browser는 이름처럼 Advertising, Browsing의 역할만 수행하는 것이 아니라 실제로는 세션 관리를 겸하고 있습니다. Lint 규칙으로 파일 길이 400줄을 초과하는 문제도 있지만 객체의 역할이 섞여 명확하게 알아볼 수 없다는 문제점, 서로의 로직이 섞일 수 있다는 문제점이 존재합니다.
개선 제안