상세 컨텐츠

본문 제목

재사용 가능한 ViewModel 구현하기

iOS

by 쑤야. 2024. 2. 18. 11:38

본문

상황


A/B 프로젝트에서 토픽 조회 API와 투표 API를 3개의 탭에서 사용한다. 각 ViewModel에 동일한 로직을 3번이나 구현하는 것은 번거로운 일이라고 생각했다. 이를 개선하기 위해서 한 번만 구현하고, 재사용할 수 있는 코드를 작성해야겠다는 생각을 하게 되었다. 

 

개선


평소 책임 지향 설계를 위해 프로토콜로 ViewModel이 가지는 책임을 먼저 정의하고, 이후 구현체를 구현하는 방식으로 ViewModel을 구현한다. 또한 프로토콜 초기 구현 개념을 쏠쏠히 사용하고 있다.

 

평소 애용하던 프로토콜과 초기 구현을 활용하여 ViewModel을 구현하면 재사용성 가능한 ViewModel을 구현할 수 있겠다는 생각을 하게 되었다. 

 

아이디어를 바탕으로 토픽 조회 ViewModel 프로토콜을 생성하고, 초기 구현으로 API 요청 로직을 작성해보았다.

 

1. FetchTopicQuery


토픽 조회는 3가지 탭에서 사용되며, 각각의 탭에서 사용하는 쿼리 값은 다르다. 

 

각 ViewModel 구현체에서 쿼리값을 지정할 수 있도록 아래와 같이 선언했다. 

 

 

2. TopicItemViewModel


이번 프로젝트를 진행하면서 Cell에 데이터를 채우기 위한 용도로 ItemViewModel 구조체를 선언해 활용하고 있다. 

 

각 ItemViewModel에서 데이터 가공을 할 수 있도록 토픽 엔티티 프로퍼티를 추가했으며, 이외에는 토픽 조회, 투표 등 ViewModel 프로토콜에서 필요한 프로퍼티들을 추가하였다.

 

(이 부분은 View에서 ViewModel로부터 ItemViewModel 타입 데이터를 전달받아, 데이터를 채울 수 있도록 하는 것이 좋지 않을까 싶어 이렇게 작성했던 것인데, 사용하면서 불편한 부분들이 있어서 ViewModel에서 그냥 토픽 엔티티로 데이터를 저장하고, fill() 메서드에서 매개변수로 ItemViewModel 타입을 받도록 하면 좋지 않을까 싶긴 하다.)

 

 

3. FetchTopicViewModel


토픽 조회가 가능한 화면의 ViewModel에서 필요한 책임, 데이터들을 정의하였으며, 프로토콜 초기 구현을 통해 API 연결 코드를 작성하였다. 

 

 

4. SideAViewModel


이제부터는 적용 코드이다.

 

사이드 A 탭에서 토픽 조회와 토픽 투표 기능이 필요하므로 SideAViewModel에 FetchTopicViewModel과 VoteTopicViewModel 프로토콜들을 채택시켰다.

(VoteTopicViewModele도 FetchTopicViewModel과 동일한 방식으로 구현했다.)

 

원래대로라면 DefaultSideAViewModel과 다른 2개의 탭 ViewModel에서 fetchTopics() 메서드와 fetchNextPage() 메서드, hasNextPage() 메서드를 모두 구현해야 하지만, 초기 구현을 그대로 사용할 예정이므로 구현체에서 구현할 필요가 없다. 

 

 

5. SideATopicItemViewModel


 

중간에 마주한 문제 상황


원래는 ItemViewModel을 TopicItemViewModel이라는 프로토콜을 선언하고 TopicItemViewModel로 토픽 데이터를 저장하려고 했었다. 

 

 

 

 

하지만 에러 발생.

Command SwiftCompile failed with a nonzero exit code

 

 

디버깅을 해본 결과, topics 프로퍼티를 get으로만 설정할 때는 문제가 없었지만, set도 설정하게 될 경우 위의 에러가 발생하는 것을 확인할 수 있었다. 

 

 

하지만 정확한 원인이 무엇이지 확인할 수가 없어 컴파일 로그를 확인해 보았다. 

 

이를 통해 대충 타입 체킹 관련 문제라는 것을 유추할 수 있었다. 반복해서 등장한 문구로 구글링을 해보았다. 

While type-checking statement at

 

https://forums.developer.apple.com/forums/thread/720345

 

Compiler crash with protocol and a… | Apple Developer Forums

This code is crashing the compiler. The compiler should never crash; it should either work or generate a meaningful diagnostic. So, issues like this are automatically bugworthy. For general advice on how to file bugs, see Bug Reporting: How and Why?. Howev

forums.developer.apple.com

 

그리고 공식 문서에서의 associatedType 설명을 참고했다. 

When defining a protocol, it’s sometimes useful to declare one or more associated types as part of the protocol’s definition. An associated type gives a placeholder name to a type that’s used as part of the protocol. The actual type to use for that associated type isn’t specified until the protocol is adopted. Associated types are specified with the associatedtype keyword.

 

위의 정보들을 통해 associatedType은 프로토콜이 채택이 되기 전까지는 타입이 아니고, placeholder처럼 단지 이름을 나타낼 뿐이다. 내가 위에서 작성한 코드는 프로토콜 채택이 되지 않은 상태 즉 ItemViewModel의 타입이 결정되기 전, set을 사용했기 때문에 나타난 문제라고 이해했다. 

 

따라서 아래와 같이 TopicItemViewModel 구조체를 생성하고, 구조체는 상속이 불가능하기 때문에 각 ItemViewModel의 프로토콜을 생성해 채택하는 방식을 활용하여 문제를 해결하였다. 

 

 

관련글 더보기