[iOS] 역제네릭(Opaque Types)을 활용한 DTO Model 구조화

유인호·2024년 6월 7일
0

iOS

목록 보기
51/64

0. 서론

서버에서 받아오는 값과, 실제로 사용할 값의 타입이나 값들을 수정하고 싶을때, ViewModel에서 변환시키거나 하는 경우가 대부분 일 것이다. 하지만 더욱 더 일관되게 Model을 사용하고 싶을 때 DTO Model을 사용할 수 있을 것이다.

그러나 어떤 모델은 그냥 사용하고, 어떤 모델은 DTO를 사용하고, 어떤 모델은 Model안에 Computed Property로 값을 수정하였다면 일관성이 없을 것이다. 이번에는 내 DIP를 배우며 떠올린 역제네릭을 활용한 DTO Model의 구조화에 대해 포스팅 할 것이다.

1. 아이디어

  1. Moya의 Router Pattern을 거쳐 받아온 모든 Model은 DTO 변환 과정을 거쳐야 한다. (실제 사용할때와 받아올때와 같은 모델이라도)
  2. 서버에서 받아온 모델에서 DTO모델로 변환한다.
  3. protocol로 구조화 한다.
  4. 역제네릭을 사용한다

2. 역제네릭(Opaque Types)은 무엇인가?

SwiftUI) ContentView 이해하기 (1/2) - some View가 뭔가요?를 참고하자면,
제네릭의 경우 함수 구현 내에서는 값의 실제 타입에 대해서 알 수 없음(숨겨짐)

불투명한 타입의 경우, 외부에서 함수의 반환 값 유형을 정확하게 알 수 없음
다만 함수 내부에서는 어떤 타입을 다루는지 정확히 알고 있음

3. 어떻게 구현하였는가?

사실 코드는 되게 간단하다.

protocol DTOModel { }

protocol DTODecodable: Decodable {
	associatedtype M: DTOModel
	func asDTOModel() -> M
}

이게 전부다.

하나의 모델의 예시를 들자면,

// 서버에서 받아온 모델
struct UserReusultModel: DTODecodable {

	let user_id: String
	let email: String
	let nickname: String
	let profileImage: String
	let phone: String
	let provider: String
	let createdAt: String
	let token: Token

	func asDTOModel() -> UserDTOModel {
		let date = DateFormatter.defaultFormatter.date(from: createdAt) ?? Date()
		return UserDTOModel(
			userID: user_id,
			email: email,
			nickName: nickname,
			profileImage: profileImage,
			phone: phone,
			provider: provider,
			createdAt: date,
			accessToken: token.accessToken,
			refreshToken: token.refreshToken
		)
	}
}

// DTO모델
struct UserDTOModel: DTOModel {
	let userID: String
	let email: String
	let nickName: String
	let profileImage: String
	let phone: String
	let provider: String
	let createdAt: Date
	let accessToken: String
	let refreshToken: String
}

// Request Repository 구현부
func validationEmail(query: EmailValidationRequestModel) -> Single<EmailValidationDTOModel> {
		Single<EmailValidationDTOModel>.create { single in

			self.provider.rx.request(.validationEmail(query: query))
				.filterSuccessfulStatusCodes()
				.map(EmailValidationResultModel.self)
				.subscribe(with: self) { _, result in
					single(.success(result.asDTOModel()))
				} onFailure: { _, error in
					single(.failure(error))
				}.disposed(by: self.disposeBag)

			return Disposables.create()
		}
	}

이런식으로 구조화하여 조금 더 깔끔하고 일관성 있는 코드를 작성 할 수 있을 것이다.

profile
🍎Apple Developer Academy @ POSTECH 2nd, 🌱SeSAC iOS 4th

0개의 댓글