피플 앱 (이하 "피플") 을 정신없이 만들다보니 계속 신경쓰이는 부분이 있었다.
"피플"은 10개정도의 Manager 들에 의해 BackEnd 소스들과 http 통신이 이루어진다. 가장 많은 코드를 가진 Manager은 역시 사연부분을 담당하는 DontionDatabaseManager 이다.
리팩터링을 진행하기 전까지 약 870줄 정도의 코드를 가진 Massive한 파일이다.
1. Decode
HTTP 통신을 많이하는 앱을 만들어보았다면 Decode를 해야하는 모델이 상당히 많다는 것을 알고 있을 것이다. 디코딩을 매우 편리하게 도와주는 퀵타입의 존재도 분명히 알고 있을 것이다.
private func decodeNoticeModel(of jsonData: Data) -> [NoticeModel]? {
do {
let noticeArray = try JSONDecoder().decode([NoticeModel].self, from: jsonData)
return noticeArray
} catch {
print("🚫 [공지] 디코딩 실패 !:\(error)")
return nil
}
}
이전까지는 이렇게 각 모델마다 디코딩을 해주는 코드를 작성해 디코딩을 해주었다.
하지만 하나 둘 쌓이는 모델들...
이렇게 디코딩을 하게되면 모델 수만큼 디코딩을 해주어야 하고 그만큼 코드의 양도 방대해지는 불상사가 일어나게 된다. (지금까지는 이런 멍청한 방법을 사용해왔다.)
나름 중복코드를 줄이고자 10개 정도의 Manager들이 상속받고 있었던 CommonBackendType에 공통 Decode 코드를 추가해주었다.
func decode<T: Decodable>(jsonData: Data, type: T.Type) -> T? {
do {
let data = try JSONDecoder().decode(type.self, from: jsonData)
return data
} catch {
return nil
}
}
코드를 잘 살펴보면 제네릭 선언부의 T를 제외하고도 returnType이라는 파라미터에 타입자체를 넣어주었다.
단톡방에 물어보니 , 꺽새 안에 들어가는 T는 제네릭 함수에 T의 제약만을 걸어주는 것이지 T에 대한 제네릭 함수이다 라는 것을 알려주지 않는다고 한다.
예를 들어, returnType을 적지 않고 그대로 T의 옵셔널을 리턴해준다면 decode함수 선언부에서는 에러가 나지 않지만, decode를 가져다 사용하는 부분에서 분명 에러가 날 것이다.
generic function을 specify 하라는 에러가 나올 것이다.
T의 타입을 정확히 규정해달라는 말이다.
Bad
func test<T: Decodable>(data: Data) -> T? {
do {
let data = try JSONDecoder().decode(T.self, from: data)
return data
} catch {
return nil
}
}
이런 함수를 사용한다고 해보자
if let returnData = self.test(jsonData: data)
data를 나쁜 함수로 디코딩을 해준다면,
정확히 T를 추론할 수 없다고 나온다.
(Generic paramter 'T' could not be inferred)
그렇다면 타입을 직접 입력해주면 ?
if let returnData = self.test<T>(jsonData: data) {
Specialize 할 수 없다는 에러를 뱉는다.
(Cannot explicitly specialize a generic function)
결론
제네릭 함수를 만들고 리턴을 제네릭으로 해야한다면 정확한 타입을 파라미터로 받아주자!