nueijeel

[Android] 세모반 리팩토링 - (1) Retrofit Client의 범용성 확장 본문

Android/에러 및 문제 해결

[Android] 세모반 리팩토링 - (1) Retrofit Client의 범용성 확장

nueijeel 2024. 5. 28. 16:01

 

세모반 리팩토링 및 버그 해결이 진행되는 동안 정리할만한 내용들을 기록해보려고 한다.

첫 번째로는 Retrofit Client 관련한 리팩토링에 대한 포스팅이다.

 

 


 

 

세모반 AOS는 3명이 담당하고 있어서 각자 맡은 부분을 따로 구현하고 별다른 코드리뷰는 진행하지 않고 있다.

 

 

그래서인지 api 호출에 필요한 레트로핏 인스턴스 생성 코드도 화면별로 다 따로 만들어져있어서

많은 코드가 중복되기도 하고,

개발 서버와 운영 서버 도메인을 바꿔가며 테스트할 때 baseURL에 들어가는 값을 바꿔줘야하는 번거로움도 있었다.

 

AWS S3 관련 api 호출에 사용되는 Retrofit 클래스

 

Excreta 관련 api 호출에 사용되는 Retrofit 클래스

 

 

이런 점을 해결하기 위해 Retrofit Client 클래스를 공통으로 사용할 수 있도록 합쳐보았다.

 

 

 

우선 저 과정을 공통적으로 수행하게 하기 위해

레트로핏 클라이언트 생성 시 매번 다르게 입력되는 api 인터페이스 클래스 타입을 제네릭을 사용해 일반화시켜야 했다.

 

 

 

제네릭(Generic)이란?

제네릭은 클래스나 메서드, 프로퍼티를 정의할 때 데이터 타입을 명시하는 대신 변수로 지정하고, 사용할 때 그 타입을 정해줌으로써 데이터 타입을 일반화하는 것이다.

 

변수로 지정한 데이터 타입을 'Type Parameter'라고 하고, 사용할 때 명시하는 구체적인 타입을 'Type Argument'라고 한다.

 

 

RetrofitClient 클래스를 제네릭 클래스로 선언하여 내부에서 제네릭을 사용

 

RetrofitClient 클래스를 제네릭 클래스로 선언해 내부에서 타입 파라미터를 사용해보려고 했으나

다음과 같은 오류가 발생했다.

 

 

Cannot use 'T' as reified type parameter. Use a class instead.

- 'T'를 reified type parameter로 사용할 수 없습니다. 대신 클래스를 사용하세요.

 

 

 

T로 여러 타입을 받아 사용할 수 있을 줄 알았는데

왜 사용할 수 없다고 하는걸까?

 

 

문제는 제네릭의 '타입 소거'에 있다.

 

 

 

타입 소거(Type Erasure)란?

코틀린이 수행하는 타입 안전성 검사는 컴파일 타임에 수행되고, 런타임 시에 인스턴스는 타입 소거에 의해 타입 인자 정보를 갖고 있지 않는다. 말그대로 타입이 지워진다는 의미의 타입 소거이다.

 

 

 

따라서 제네릭 클래스의 인스턴스가 있어도 그 인스턴스를 생성할 때 사용한 타입 인자의 정보를 알아낼 수가 없다.

 

앞서 코드의 T::class.java 부분에서 오류가 발생했던 것도

런타임 시 T의 타입 소거로 인자 정보가 지워져 타입 정보를 가져올 수 없었기 때문이다.

 

 

 

하지만 코틀린의 경우 타입 소거로 인한 제약을 없애는 inline 함수가 있다.

 

 

 

Inline 함수

inline 함수는 컴파일러에 의해 해당 함수 호출 부분이 전부 함수 본문으로 바뀐다. 이때 reified 키워드가 적용된 제네릭 타입 파라미터는 구체적인 타입으로 대체되기 때문에 런타임에도 해당 타입 정보를 유지할 수 있다.

 

 

따라서 컴파일러는 실체화한 타입 인자를 사용해 inline 함수를 호출하는 각 부분의 정확한 타입 인자를 알 수 있다!!

-> 타입 소거의 영향을 받지 않음

 

 

이 원리를 이용해서 Retrofit Client 클래스를 수정해보면

 

Retrofit 객체를 만들어주고

inline 함수를 통해 실체화된 제네릭 타입을 가지고 인터페이스 구현체를 생성할 수 있게 해준다.

 

 

 

결과적으로 여러 레포지토리 구현체에서 retrofitClient.createApi 함수의 타입 인자로 api 인터페이스를 직접 지정하여 api 객체를 생성해 사용할 수 있게 되었다!

 

 

 

 

 

728x90